fix: shared Roslyn worker ReadLine no longer blocks Editor shutdown

Merged
HA
3mo ago
+589-82

Summary

  • Domain reload / Editor quit can interrupt a stuck shared Roslyn worker read without waiting up to 30s on the sync lock
  • Dynamic-code compile conversations stay single-flight (write→read), while timeout paths kill the worker and observe abandoned reads

User Impact

  • Before: A blocked worker ReadLine held the session lock, so shutdown (reload/quit) could stall the Editor for the full response timeout
  • After: Shutdown kills the worker under a short state lock and does not wait on the compile gate; in-flight readers fail fast when pipes close

Lock boundary (before / after)

BEFORE
┌─────────────────────────────────────────────────────────┐
│ ExecuteLocked(_syncRoot)                                │
│   write request                                         │
│   ReadLine()  ← blocks up to 30s WHILE HOLDING LOCK     │
│   read diagnostics                                      │
└─────────────────────────────────────────────────────────┘
ShutdownProcessLocked ALSO requires _syncRoot
→ shutdown waits behind the stuck ReadLine

AFTER
┌─ SemaphoreSlim compile gate (async, serializes conversations)
│    short _syncRoot: write + take StreamReader
│    await ReadLineAsync  ← OUTSIDE _syncRoot
│    short _syncRoot only if kill/retry needed
└─────────────────────────────────────────────────────────
Shutdown: lock(_syncRoot) only → kill/close pipes
          does NOT WaitAsync on the compile gate
→ stuck read unblocks via pipe close; abandoned Task observed

Changes

  • Extract Unity-free SharedRoslynCompilerWorkerLineReader (timeout/cancel + observe abandoned ReadLine)
  • Extract SharedRoslynCompilerWorkerSessionCoordination (async compile gate + short state lock + shutdown bypass)
  • TryCompile → TryCompileAsync; host kills worker on empty/invalid/timeout responses
  • Pure unit tests for line reader + “shutdown while compile gate held”
  • Wire tests into EditMode / compile-check CI workflows

Out of scope (umbrella note)

  • SharedRoslynCompilerWorkerAssemblyBuilder still uses sync WaitForExit for worker DLL builds; call sites can run on the Unity main thread during compile/prewarm

Verification

  • dotnet test tests/SharedRoslynCompilerWorker.UnitTests/... (8 passed)
  • Unity Bee compiled ExecuteDynamicCode.Editor with no CS errors for these files
  • Local uloop compile hung after domain reload in this environment (IPC warmup hit disposed DynamicCodeExecutionScheduler); CI compile check is the authoritative follow-up

Test plan

  • CI: SharedRoslynCompilerWorker unit tests green
  • CI / local: EditMode SharedRoslynCompilerWorkerHostTests still pass
  • Manual: trigger domain reload during a slow dynamic-code compile; Editor should not stick on worker ReadLine