fix(bloc): make isClosed return true immediately when close is called#4759
fix(bloc): make isClosed return true immediately when close is called#4759ersanKolay wants to merge 2 commits intofelangel:masterfrom
Conversation
Previously isClosed was based on _stateController.isClosed, which only became true after the full async teardown completed. In Bloc, the _eventController closes first during close(), creating a window where isClosed returned false but add() would throw a StateError. This introduces a _closed flag set at the start of close(), and overrides isClosed in Bloc to also check _eventController.isClosed. Internal emit/onEmit checks use _stateController.isClosed directly so in-progress handlers can still emit during teardown. Closes felangel#4749
|
Thanks for the PR but I’m not sure this is the correct fix. I think the first step here is to make a minimal reproduction sample. The tests you added don’t reflect the issue linked. |
Adds a test that reproduces the exact scenario from felangel#4749: a Bloc using emit.onEach where the onData callback guards add() with isClosed, but external stream emits during close() teardown causing a StateError because _eventController closes before _stateController.
|
Thanks for the feedback. I added a minimal reproduction sample as a comment on #4749 and updated the PR with a test that reproduces the exact scenario from the issue: a Bloc using Let me know if you'd like a different approach for the fix itself. |
Thanks I'll take a look shortly! 🙏 |
Summary
isClosedreturnedfalseduringBloc.close()teardown, butadd()would throw aStateErrorbecause the internal_eventControllerwas already closed.isClosedwas based on_stateController.isClosed, butBloc.close()closes_eventControllerfirst. This created a window where checkingisClosedbeforeadd()was unreliable._closedflag inBlocBaseset at the start ofclose(), and overridesisClosedinBlocto also check_eventController.isClosed.emit/onEmitchecks use_stateController.isCloseddirectly so in-progress event handlers can still emit states during teardown.Closes #4749
Test plan
Bloc.isClosedreturns true immediately whenclose()is called (without await)Cubit.isClosedreturns true immediately whenclose()is called (without await)dart analyzeclean (no errors or warnings)