Skip to content

fix(core): isolate sessions to prevent old/new interference#28

Merged
missuo merged 4 commits into
missuo:mainfrom
erning:fix/session-isolation
Mar 29, 2026
Merged

fix(core): isolate sessions to prevent old/new interference#28
missuo merged 4 commits into
missuo:mainfrom
erning:fix/session-isolation

Conversation

@erning

@erning erning commented Mar 29, 2026

Copy link
Copy Markdown
Collaborator

Problem

Sessions share global cancelled and session Arcs in the Core struct. When a new session starts while the old one is still finalizing (ASR/LLM/paste):

  • The old task's cleanup_session wipes the new session's state (same Arc)
  • Cancellation flags cross-contaminate between sessions
  • The 300ms delayed endSession from the old hotkey cycle kills the new session's audio channel
  • Stale callbacks from the old session overwrite the new session's UI state via dispatch_async
  • Local async blocks (paste completion, error reset, audio error recovery) also reset UI to idle during a new session

Solution

Three coordinated layers:

1. Per-session Arcs (Rust)
sp_core_session_begin creates fresh cancelled and session Arcs each time and signals the old task to cancel. The old task holds its own Arc clones — its cleanup cannot affect the new session. Cancellation checks are added both before and after the LLM call.

2. Cancellable dispatch blocks (ObjC)
The 300ms delayed endSession uses a cancellable dispatch_block_t. New holdStart/tapStart cancels any pending block so a stale endSession cannot kill the new session's audio channel.

3. Session token on all callbacks
A monotonic u64 session_token is added to SPSessionContext and threaded through all 6 session-scoped callbacks. ObjC increments the token on each beginSession and discards any callback whose token doesn't match — eliminating the dispatch_async race. The token is also exposed via SPRustBridge.currentSessionToken and used to guard local async blocks (paste completion, error reset, audio error recovery).

Files changed

  • koe-core/src/ffi.rs — session token in SPSessionContext and all callback signatures
  • koe-core/src/lib.rs — per-session Arcs, cancellation checks, token threading
  • KoeApp/Koe/Bridge/SPRustBridge.hcurrentSessionToken property
  • KoeApp/Koe/Bridge/SPRustBridge.m — token increment, stale callback filtering
  • KoeApp/Koe/AppDelegate/SPAppDelegate.m — cancellable dispatch blocks, token guards on async blocks

Test plan

  • make build passes
  • Hold-to-talk and tap-to-toggle work normally
  • Rapid re-press during 300ms window: new session works, old one silently aborts
  • Cancel during LLM correction: no stale text pasted
  • Error during old session doesn't flash error UI on new session

erning added 4 commits March 29, 2026 21:11
When a new session starts while the old one is still running (e.g.
finalizing ASR or LLM correction), they previously shared the same
cancelled flag and session Arc, causing the old task's cleanup to
wipe the new session's state.

Rust side: create fresh per-session Arcs in sp_core_session_begin
instead of reusing the global ones.  The old task keeps its own Arc
clones and exits via the cancellation signal without affecting the
new session.  Add an extra cancellation check before the LLM call
so aborted sessions exit faster.

ObjC side: use cancellable dispatch blocks for the 300ms delayed
endSession calls.  When a new session starts, cancel any pending
block from the previous hotkey cycle so a stale endSession cannot
drop the new session's audio channel.
The previous commit only checked cancellation before the LLM call.
If a new session started while the old one was awaiting LLM correction,
the old session would still paste its stale result into the new
session's window.  Add a post-LLM cancellation check.
Even with per-session Arcs and Rust-side cancellation checks, stale
callbacks from an old session can still reach the ObjC main thread
via dispatch_async after a new session has started, overwriting the
new session's UI state.

Add a u64 session_token to SPSessionContext and all session-scoped
callbacks (ready, error, warning, final_text, state_changed, interim).
ObjC increments the token on each beginSession and discards any
callback whose token does not match the current one.
The paste completion (simulatePasteWithCompletion) and error reset
(dispatch_after 2s) blocks run asynchronously on the main thread
without going through the Rust callback chain, so the session token
filter in SPRustBridge did not cover them.  If a new session starts
during the delay, the stale block would reset the UI to idle.

Expose currentSessionToken on SPRustBridge and capture it in both
blocks.  Skip the idle transition when the token no longer matches.
@missuo missuo merged commit 8b22ea8 into missuo:main Mar 29, 2026
@erning erning deleted the fix/session-isolation branch March 29, 2026 14:50
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.

2 participants