fix: emit status event before closing controller on unrecoverable stream error#324
Merged
tanderson-ld merged 1 commit intoJul 10, 2026
Conversation
…eam error StreamingDataSource's onError handler called stop(), which closes _dataController, and then added a StatusEvent to the closed controller, throwing 'Bad state: Cannot add event after closing'. Emit the event before stopping, matching PollingDataSource's ordering.
Contributor
|
Thank you for bringing this to our attention and for the contribution @johnbonds, we will review this shortly. |
tanderson-ld
approved these changes
Jul 10, 2026
1 task
tanderson-ld
added a commit
that referenced
this pull request
Jul 10, 2026
…325) ## Summary - Add `workflow_dispatch:` to `.github/workflows/release-please.yml` so the release-please workflow can be re-fired manually from the Actions UI without pushing a commit to `main`. ## Motivation When a commit lands on `main` whose message release-please can't parse (e.g. run [29123151878](https://github.com/launchdarkly/flutter-client-sdk/actions/runs/29123151878/job/86462658991), where the squash-merge body of PR #324 tripped the conventional-commits parser), the workflow completes without opening a release PR. Recovering currently requires either editing the offending PR body (to add a `BEGIN_COMMIT_OVERRIDE` block) and then pushing another commit to `main` to retrigger, or landing a follow-up commit whose sole purpose is to fire the workflow. Adding `workflow_dispatch` lets us edit the PR body and click "Run workflow" from the Actions UI instead. ## Test plan - [ ] After merge, the release-please workflow appears in the Actions UI with a "Run workflow" button on the `main` branch and re-runs cleanly on demand. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Single trigger addition on an existing release automation workflow; no change to release-please steps, permissions, or publishing behavior. > > **Overview** > Adds **`workflow_dispatch`** to **`release-please.yml`**, so release-please can be started from the GitHub Actions UI in addition to pushes to **`main`**. > > This supports recovery when a **`main`** push does not produce a release PR (for example, a squash-merge message that release-please cannot parse). After fixing the PR body or related metadata, you can re-run the workflow without pushing a no-op commit to **`main`**. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 90555f6. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requirements
Related issues
None filed — happy to open one if you prefer tracking it that way.
Describe the solution you've provided
In
StreamingDataSource, the SSE subscription'sonErrorhandler handles an unrecoverable error by callingstop()— which closes_dataController— and then adds aStatusEventto that now-closed controller:This throws
Bad state: Cannot add event after closing, which surfaces as an uncaught error in the host application (we see this in production via Crashlytics — 44 users / 65 events in the last week on our app alone, reported atstreaming_data_source.dart:203on v1.9.0; the same code is present onmain). It also means consumers never receive the finalStatusEvent, since the controller is already closed when it's added.The trigger in the field is an unrecoverable error from the event source client (e.g.
Unable to make request), which we most often see when the app resumes from background and the connection can't be re-established.This PR reorders the two statements so the
StatusEventis added beforestop()closes the controller — matching the orderingPollingDataSourcealready uses (event added first,stop()after). A regression test is included; it fails against the previous ordering and passes with this change.One thing I deliberately left unchanged for the maintainers to consider: this
StatusEventis emitted withshutdown: falseeven though the source is permanently shutting down (_permanentShutdown = true). Settingshutdown: truemight be more accurate, but it changes downstream state handling, so I kept this fix minimal.Describe alternatives you've considered
Guarding the add with
if (!_dataController.isClosed)would prevent the crash but would still silently drop the final status event; reordering both fixes the crash and delivers the event.Note
Low Risk
Small ordering fix in error shutdown with a targeted test; no auth or data-model changes.
Overview
Fixes a production crash when the SSE stream hits an unrecoverable error (e.g. after resume from background):
onErrorused to callstop()first, which closes_dataController, then add aStatusEvent—that second step threw Bad state: Cannot add event after closing and consumers never saw the final error status.The handler now emits the
StatusEventfirst, then callsstop(), aligned with howPollingDataSourceorders shutdown. A regression test asserts the status update is delivered without throwing when the mock stream reports an unrecoverable error.Reviewed by Cursor Bugbot for commit 1cec4b0. Bugbot is set up for automated code reviews on this repo. Configure here.
BEGIN_COMMIT_OVERRIDE
fix: emit status event before closing controller on unrecoverable stream error
END_COMMIT_OVERRIDE