Skip to content

Fix MissingReferenceException when stopping scene with active mic#310

Merged
MaxHeimbrock merged 1 commit into
mainfrom
max/fix-monobehaviourcontext-destroyed-coroutine
Jun 15, 2026
Merged

Fix MissingReferenceException when stopping scene with active mic#310
MaxHeimbrock merged 1 commit into
mainfrom
max/fix-monobehaviourcontext-destroyed-coroutine

Conversation

@MaxHeimbrock

@MaxHeimbrock MaxHeimbrock commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Problem

Stopping the scene with an active microphone source consistently throws:

MissingReferenceException: The object of type 'LiveKit.MonoBehaviourContext' has been destroyed but you are still trying to access it.
  at LiveKit.MonoBehaviourContext.RunCoroutine (MonoBehaviourContext.cs:39)
  at LiveKit.MicrophoneSource.Stop (MicrophoneSource.cs:148)
  at MeetManager.OnDestroy ...

On play-mode exit, Unity tears down the DontDestroyOnLoad LiveKitSDK GameObject (backing the MonoBehaviourContext singleton) before MeetManager.OnDestroy() runs. MicrophoneSource.Stop() then calls RunCoroutine, which did _instance.StartCoroutine(...) on the destroyed instance and threw.

Fix

Guard RunCoroutine: when _instance has been destroyed (Unity's overloaded == reports it as null), drain the coroutine synchronously via a new DrainCoroutine helper and invoke onComplete, instead of touching the dead MonoBehaviour. DrainCoroutine recurses into nested enumerators and skips time-based yields (meaningless during shutdown). This keeps cleanup side effects working and protects every caller — MicrophoneSource is currently the only one, but the fix lives at the shared entry point rather than at the call site.

Test

Added an EditMode regression test (Tests/EditMode/MonoBehaviourContextTests.cs) that deterministically reproduces the failure mode: it points _instance at a DestroyImmediate-d object and asserts RunCoroutine drains without throwing and still runs the body + onComplete. The original singleton is saved/restored via reflection so other tests are unaffected.

The exception fires synchronously from StartCoroutine once its host is destroyed, so the test reproduces the precondition deterministically without needing Unity's actual play-mode-exit teardown ordering. Against the pre-fix code it fails with MissingReferenceException; with the fix it passes.

On play-mode exit, Unity tears down the DontDestroyOnLoad LiveKitSDK
GameObject (backing the MonoBehaviourContext singleton) before
MeetManager.OnDestroy() runs. MicrophoneSource.Stop() then calls
RunCoroutine, which did _instance.StartCoroutine(...) on the destroyed
instance and threw a MissingReferenceException.

Guard RunCoroutine: when _instance has been destroyed (Unity's
overloaded == reports it as null), drain the coroutine synchronously via
a new DrainCoroutine helper and invoke onComplete, instead of touching
the dead MonoBehaviour. This keeps cleanup side effects working and
protects every caller, not just MicrophoneSource.

Add an EditMode regression test that deterministically reproduces the
failure mode by pointing _instance at a DestroyImmediate-d object and
asserting RunCoroutine drains without throwing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@MaxHeimbrock MaxHeimbrock merged commit 58f46a5 into main Jun 15, 2026
15 checks passed
@MaxHeimbrock MaxHeimbrock deleted the max/fix-monobehaviourcontext-destroyed-coroutine branch June 15, 2026 14:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants