Fix migration memory leak: old collection meta doc not deleted (#7791)#7792
Open
OskarD wants to merge 1 commit intopubkey:masterfrom
Open
Fix migration memory leak: old collection meta doc not deleted (#7791)#7792OskarD wants to merge 1 commit intopubkey:masterfrom
OskarD wants to merge 1 commit intopubkey:masterfrom
Conversation
…migration (pubkey#7791) The old collection meta doc deletion failed with a _rev conflict because the cached reference from the start of startMigration() became stale by the time deletion ran. The conflict was not retried, leaving the meta doc orphaned. On every app restart, mustMigrate() found this doc and returned true, spinning up leaked storage instances and subscriptions (~100 MB/min). Fix: replace single-attempt deletion with a conflict-retry loop that re-fetches the doc before each attempt. Also add .catch() to the fire-and-forget startMigration() call in migratePromise() to prevent unhandled promise rejections.
Author
|
Note on .catch(() => {}) (line 585) The original code called this.startMigration(batchSize) as fire-and-forget without await or .catch(). Errors before the inner try/catch (e.g. createStorageInstance(), leader election) were already unhandled promise rejections. The .catch(() => {}) makes this explicit and prevents runtime crashes in strict environments. A better fix would wrap the entire startMigration() body in the existing try/catch so early failures also set status = ERROR and surface through the status observable. That way migratePromise() would resolve with ERROR instead of hanging. But that is a separate improvement beyond the scope of this PR. |
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.
This PR contains:
Describe the problem you have without this PR
Two bugs in the migration-schema plugin combine to create a memory leak (~100 MB/min) that worsens on every app restart (issue #7791).
Bug 1: After
startMigration()migrates all documents, it tries to delete the old collection meta doc using a reference cached at the start of the function. If the doc's_revhas changed (due to concurrent writes or storage internals), thewriteSingle()fails with a conflict. The conflict handler only ignores already-deleted docs, so the meta doc persists. On every restart,mustMigrate()finds this orphaned doc and returnstrue.Bug 2:
migratePromise()callsstartMigration()withoutawait(fire-and-forget, by design for multi-tab leader election). When combined with Bug 1, a stale DONE status doc from a previous migration run satisfies thePromise.raceimmediately, while the orphanedstartMigration()creates leaked storage instances and subscriptions. The fire-and-forget also means any errors thrown fromstartMigration()become unhandled promise rejections.Changes
updateStatus()andaddConnectedStorageToCollection())..catch(() => {})to the fire-and-forgetstartMigration()call to prevent unhandled promise rejections. Errors still propagate via the status observable.Todos