From 90aea8c535d8110f8f614caf9cc658dd228373b6 Mon Sep 17 00:00:00 2001 From: Dusty Holmes Date: Fri, 1 Mar 2024 15:41:11 -0600 Subject: [PATCH 1/3] Unload modules before disposal This is a safety mechanism because the disposal of a module will remove parts of its disposables which will cause the disposal of children to explode on unload. --- lib/src/lifecycle_module.dart | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/src/lifecycle_module.dart b/lib/src/lifecycle_module.dart index 217e7c9..e581bc6 100644 --- a/lib/src/lifecycle_module.dart +++ b/lib/src/lifecycle_module.dart @@ -879,6 +879,15 @@ abstract class LifecycleModule extends SimpleModule with Disposable { @override @protected Future onWillDispose() async { + // It is possible for child modules to be added to the list of children after unload + // but before dispose due to asynchrony. Continuing with dispose will cause these to + // be disposed which will trigger unload and cause exceptions due to the parent being + // partialy disposed. Ensure we give everything a chance to be unloaded before proceeding. + await Future.wait(_childModules + .toList() + .where((child) => !child.isUnloaded) + .map((child) => child.unload())); + if (isInstantiated || isUnloaded) { return; } From 242cf9761144a07585517f3dbc4f5b2b4db16e61 Mon Sep 17 00:00:00 2001 From: Dusty Holmes Date: Mon, 4 Mar 2024 09:00:53 -0600 Subject: [PATCH 2/3] Use a preventative solution with a proper test --- lib/src/lifecycle_module.dart | 17 ++++++++--------- test/lifecycle_module_test.dart | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/lib/src/lifecycle_module.dart b/lib/src/lifecycle_module.dart index e581bc6..b383829 100644 --- a/lib/src/lifecycle_module.dart +++ b/lib/src/lifecycle_module.dart @@ -507,6 +507,14 @@ abstract class LifecycleModule extends SimpleModule with Disposable { final completer = Completer(); onWillLoadChildModule(childModule).then((_) async { + // It is possible to reach this point due to the asynchrony of onWillLoadChildModule. + // In that case, simply do not load the child module and instead dispose it. + if (isUnloaded || isUnloading) { + await childModule.dispose(); + completer.complete(); + return; + } + _willLoadChildModuleController.add(childModule); final childModuleWillUnloadSub = listenToStream( @@ -879,15 +887,6 @@ abstract class LifecycleModule extends SimpleModule with Disposable { @override @protected Future onWillDispose() async { - // It is possible for child modules to be added to the list of children after unload - // but before dispose due to asynchrony. Continuing with dispose will cause these to - // be disposed which will trigger unload and cause exceptions due to the parent being - // partialy disposed. Ensure we give everything a chance to be unloaded before proceeding. - await Future.wait(_childModules - .toList() - .where((child) => !child.isUnloaded) - .map((child) => child.unload())); - if (isInstantiated || isUnloaded) { return; } diff --git a/test/lifecycle_module_test.dart b/test/lifecycle_module_test.dart index a819918..55e4f40 100644 --- a/test/lifecycle_module_test.dart +++ b/test/lifecycle_module_test.dart @@ -1756,6 +1756,29 @@ void runTests(bool runSpanTests) { childModule.eventList, equals(['willLoad', 'onLoad', 'didLoad'])); }); + test('followed by parent unload causes child to dispose and never load', + () async { + parentModule.eventList?.clear(); + final load = parentModule.loadChildModule(childModule); + + await parentModule.unload(); + await load; + + expect( + parentModule.eventList, + equals([ + 'onWillLoadChildModule', + 'onShouldUnload', + 'willUnload', + 'onUnload', + 'didUnload', + 'onDispose' + ]), + ); + + expect(childModule.eventList, equals(['onDispose'])); + }); + test('should emit lifecycle log events', () async { expect( Logger.root.onRecord, From c012217518a76c40e730697c1585e0e9914798af Mon Sep 17 00:00:00 2001 From: Dusty Holmes Date: Mon, 4 Mar 2024 10:56:05 -0600 Subject: [PATCH 3/3] Update the changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f258c5a..94e27fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [3.0.6](https://github.com/Workiva/w_module/compare/3.0.5...3.0.6) + +_March 4, 2024_ + +- **Bug Fix:** A child module which loads during the unload of the parent + may cause BadState exceptions. + ## [3.0.0](https://github.com/Workiva/w_module/compare/2.0.5...3.0.0) _August 18, 2023_