Dispose the source info the processing creates itself - #7
Merged
Conversation
Kentarohakase
force-pushed
the
refactor/shared-audio-thresholds
branch
from
August 12, 2026 00:56
eab2d29 to
57aef9c
Compare
Kentarohakase
force-pushed
the
fix/source-info-subscription-leak
branch
from
August 12, 2026 00:56
5bf4014 to
aba512b
Compare
Kentarohakase
force-pushed
the
refactor/shared-audio-thresholds
branch
from
August 12, 2026 01:08
57aef9c to
153c588
Compare
AudioInfo subscribes to the localization service in its constructor so its display strings follow a language change, and it unsubscribes only when it is disposed. That service lives for the whole session, so an instance nobody disposes stays reachable through it until the app closes. ProcessAsync analyses the source itself when the caller supplies none, and that instance was assigned to a local and then dropped. Every run down that path left one behind, and each one kept getting notified on every later language change. The rest of the method moves into RenderAsync so the created instance can be held in a using without re-indenting ninety lines. The caller-supplied instance takes the same path but is not disposed there, because it belongs to the queue item that hands it in. The new test pins the contract that makes disposal matter: a disposed AudioInfo no longer reacts to a language change.
Kentarohakase
changed the base branch from
refactor/shared-audio-thresholds
to
main
August 12, 2026 01:10
Kentarohakase
force-pushed
the
fix/source-info-subscription-leak
branch
from
August 12, 2026 01:10
aba512b to
2329e41
Compare
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.
Stacked on #6. Not from the improvement plan — this came out of the survey that produced it, and it is a real leak rather than a cleanup.
The leak
AudioInfosubscribes toLocalizationService.Instance.PropertyChangedin its constructor so its display strings follow a language change, and it unsubscribes only inDispose():LocalizationService.Instanceis a process-lifetime singleton, so anAudioInfothat nobody disposes stays reachable through that subscription until the app closes.AudioProcessingService.ProcessAsyncanalyses the source itself when the caller supplies none:Every run down that path left one behind. They are not merely retained: each one keeps receiving
OnLocalizationChangedfor every later language switch, so the cost grows with the number of runs.The fix
The remainder of
ProcessAsyncmoves intoRenderAsync, which lets the created instance be held in ausingwithout re-indenting ninety lines of an untested method:Ownership is the point of the split: an
AudioInfohanded in through the options belongs to theBatchProcessingItemand must not be disposed here, so only the one created locally is.ProcessAsynchad no tests, so the extraction was verified to be a pure move —git diff -wshows nothing but the new wrapper, with the entire body fromEnsureSufficientDiskSpaceonward untouched.Scope check
I looked for the same pattern elsewhere.
FFprobeServiceandAudioDiagnosticsServicecreate instances and hand ownership to their caller, which is correct.AudioInfo.WithSelectedAudioStreamreturns a new instance intoBatchProcessingItem.SetAudioInfo, which disposes the previous one and is guarded by aReferenceEqualscheck, so the no-streams case that returnsthiscannot dispose and store the same instance at once. That guard is now covered by a test, since it is load-bearing and easy to remove by accident.A note on the new test
Dispose_StopsListeningForLanguageChangespassed in isolation but failed in the full run: it assumed the process was not already on English, and assigning the same culture raises no change at all. It now establishes a known starting culture first. Worth mentioning because it is the same class of cross-test coupling that #4 addressed — that one was a race, this one was an ordering assumption.Verification
No changelog entry
The leak is invisible until a long session with many runs, and no behaviour a user can point at changes.