Bug Description
The Eclipse UI freezes permanently when a 402 quota error triggers the fallback model flow. A deadlock occurs between the main (UI) thread and the LSP message listener thread.
Deadlock Chain
Thread 1: "main" (UI thread) — WAITING
CompletableFuture.get() ← blocks UI thread indefinitely
ChatBaseService.getPersistentFilePath(:125) ← lsConnection.persistence().get()
ChatBaseService.persistUserPreference(:86) ← synchronized method
ModelService.setActiveModel(:351)
ModelService.setFallBackModelAsActiveModel(:390)
ChatContentViewer.lambda$2(:240) ← SWT async runnable on UI thread
SWT Display.readAndDispatch event loop
Thread 2: "LS-...#listener-0" (LSP listener) — WAITING
Display.syncExec() ← blocks waiting for UI thread
SwtUtils.invokeOnDisplayThread(:81)
ChatContentViewer.processTurnEvent(:171)
ChatView.onChatProgress(:881)
CopilotLanguageClient.notifyProgress(:374)
LSP4J StreamMessageProducer.listen ← single-threaded message reader
What Happens
- User hits a 402 quota error →
ChatContentViewer (line 238–240) calls setFallBackModelAsActiveModel() on the UI thread
- That calls
persistUserPreference() → getPersistentFilePath() → CompletableFuture.get() — blocking the UI thread waiting for the LSP server to respond
- Meanwhile, the Copilot language server sends a chat progress notification
- The LSP listener thread tries to deliver it via
Display.syncExec() — which needs the UI thread to dispatch
- The UI thread is blocked waiting on the LSP response, and the LSP listener is the only thread reading server messages — so the persistence response can never arrive
Result: Classic circular wait → permanent UI freeze.
Root Cause
ChatBaseService.getPersistentFilePath() (line 125) calls this.lsConnection.persistence().get() — an unbounded blocking call — inside a synchronized method that executes on the UI thread. This violates the Eclipse threading rule: never block the UI thread on async results that depend on the same event loop.
Suggested Fixes
- Primary: Don't call
.get() on the UI thread in getPersistentFilePath(). Cache the persistence path eagerly at startup or use .thenAccept() to persist asynchronously.
- Secondary: Change
processTurnEvent() from syncExec to asyncExec where possible, so the LSP listener thread isn't blocked by the UI thread.
- Safety net: At minimum, add a timeout —
.get(5, TimeUnit.SECONDS) — to prevent a permanent freeze.
Reproduction
Trigger a 402 quota exceeded error while a chat response is actively streaming (so the LSP listener thread is busy delivering progress notifications via syncExec).
Environment
- macOS (aarch64)
- Java 21.0.1+12-LTS
- Eclipse 2025-12 (4.37+)
Bug Description
The Eclipse UI freezes permanently when a 402 quota error triggers the fallback model flow. A deadlock occurs between the main (UI) thread and the LSP message listener thread.
Deadlock Chain
Thread 1:
"main"(UI thread) — WAITINGThread 2:
"LS-...#listener-0"(LSP listener) — WAITINGWhat Happens
ChatContentViewer(line 238–240) callssetFallBackModelAsActiveModel()on the UI threadpersistUserPreference()→getPersistentFilePath()→CompletableFuture.get()— blocking the UI thread waiting for the LSP server to respondDisplay.syncExec()— which needs the UI thread to dispatchResult: Classic circular wait → permanent UI freeze.
Root Cause
ChatBaseService.getPersistentFilePath()(line 125) callsthis.lsConnection.persistence().get()— an unbounded blocking call — inside asynchronizedmethod that executes on the UI thread. This violates the Eclipse threading rule: never block the UI thread on async results that depend on the same event loop.Suggested Fixes
.get()on the UI thread ingetPersistentFilePath(). Cache the persistence path eagerly at startup or use.thenAccept()to persist asynchronously.processTurnEvent()fromsyncExectoasyncExecwhere possible, so the LSP listener thread isn't blocked by the UI thread..get(5, TimeUnit.SECONDS)— to prevent a permanent freeze.Reproduction
Trigger a 402 quota exceeded error while a chat response is actively streaming (so the LSP listener thread is busy delivering progress notifications via
syncExec).Environment