{"record":{"id":"8c3cdd6ae1a80769","repo":"File-New-Project/EarTrumpet","slug":"device-session-parent-is-invalid-but-device-is-sti","errorCode":null,"errorMessage":"Device session parent is invalid but device is still notifying.","messagePattern":"Device session parent is invalid but device is still notifying\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"warning","filePath":"EarTrumpet/DataModel/WindowsAudio/Internal/AudioDeviceSessionCollection.cs","lineNumber":65,"sourceCode":"            {\r\n                session.PropertyChanged -= Session_PropertyChanged;\r\n            }\r\n\r\n            foreach (var session in _movedSessions)\r\n            {\r\n                session.PropertyChanged -= MovedSession_PropertyChanged;\r\n            }\r\n\r\n            _sessionManager.UnregisterSessionNotification(this);\r\n        }\r\n\r\n        private void CreateAndAddSession(IAudioSessionControl session)\r\n        {\r\n            try\r\n            {\r\n                if (!_parent.TryGetTarget(out IAudioDevice parent))\r\n                {\r\n                    throw new Exception(\"Device session parent is invalid but device is still notifying.\");\r\n                }\r\n\r\n                var newSession = new AudioDeviceSession(parent, session, _dispatcher);\r\n                _dispatcher.BeginInvoke((Action)(() =>\r\n                {\r\n                    if (newSession.State == SessionState.Moved)\r\n                    {\r\n                        _movedSessions.Add(newSession);\r\n                        newSession.PropertyChanged += MovedSession_PropertyChanged;\r\n                    }\r\n                    else if (newSession.State != SessionState.Expired)\r\n                    {\r\n                        AddSession(newSession);\r\n                    }\r\n                }));\r\n            }\r\n            catch (Exception ex)\r\n            {\r","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/File-New-Project/EarTrumpet/blob/aa894e51c22f5f9a939b31b224c4d2d3e163416e/EarTrumpet/DataModel/WindowsAudio/Internal/AudioDeviceSessionCollection.cs#L47-L83","documentation":"AudioDeviceSessionCollection.CreateAndAddSession is invoked from a COM session-notification callback (OnSessionCreated) and during enumeration. The parent IAudioDevice is held only via a WeakReference, so if the device object has been garbage-collected while the unmanaged IAudioSessionManager2 still holds the notification sink, _parent.TryGetTarget fails and the code throws to signal the inconsistency. Importantly the throw is inside a try block that catches all exceptions and logs them (line 82), so it does not propagate to the COM caller.","triggerScenarios":"A new audio session notification arrives (OnSessionCreated) or an enumerated session is being added after the owning IAudioDevice was already collected — device removed/disposed while a late callback is in flight.","commonSituations":"Device disconnect/removal racing with GC and a pending session-created callback; finalizer ordering where the device dies before the collection unregisters its notification.","solutions":["Keep the existing try/catch in CreateAndAddSession (it already swallows and logs) — do not let this reach the COM boundary.","Ensure the device's lifetime outlives UnregisterSessionNotification (unregister in Dispose, not only in the finalizer).","If surfacing this in diagnostics, treat it as benign and lower its severity.","Avoid constructing the collection for a device that is already being torn down."],"exampleFix":"// the throw is already guarded; ensure unregister happens deterministically\n// before: only finalizer unregisters\n// after:\npublic void Dispose()\n{\n    _sessionManager.UnregisterSessionNotification(this);\n    GC.SuppressFinalize(this);\n}","handlingStrategy":"try-catch","validationCode":"// guard the weak parent before use\nif (!_parent.TryGetTarget(out IAudioDevice parent))\n{\n    Trace.WriteLine(\"Session parent collected; ignoring late notification.\");\n    return;\n}","typeGuard":null,"tryCatchPattern":"// CreateAndAddSession already wraps the throw in try/catch that logs; keep it.\ntry { /* create + add session */ } catch (Exception ex) { Trace.WriteLine($\"CreateAndAddSession {ex}\"); }","preventionTips":["Unregister COM session notifications in Dispose, not only the finalizer, to shorten the race window.","Treat this exception as benign (logged) — never let it propagate to the COM callback boundary.","Avoid keeping the device alive solely by the WeakReference; own its lifetime explicitly."],"tags":["audio","com","weakreference","race-condition","device-session","gc"],"backgroundTag":null,"analyzedSha":"aa894e51c22f5f9a939b31b224c4d2d3e163416e","analyzedAt":"2026-08-13T18:51:30.564Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}