uttrflow / uttrflow/uttrflow-swift
KeyInterceptor's stop-before-run race relies on run loop behaviour rather than an explicit handshake
- Dominant language
- Swift
- Stars
- 4
- Forks
- 17
- Avg merge
- 3h 32m
- Merged PRs (30d)
- 277
Description
## What happens
`InterceptorTap.serve` (`Sources/UttrflowInput/KeyInterceptor.swift:182-195`) registers the run loop and adds the tap's source under the lifecycle lock, then releases the lock, enables the tap and calls `CFRunLoopRun()`. `stop()` (lines 198-213) invalidates the source and port and calls `CFRunLoopStop` on the registered loop.
If `stop()` runs after the source is added but before the thread enters `CFRunLoopRun()`, the `CFRunLoopStop` is sent to a loop that is not running yet and has no effect. Shutdown then depends on `CFRunLoopRun()` returning at once because its only source has been invalidated. #382 noted this under "least sure of": the tests exercised it, but it rests on Core Foundation's behaviour rather than on an explicit handshake.
## Why it matters
If the run loop ever does not return, the tap thread and the retained state stay alive after `stop()`, and a later tap can be started while an old one lingers. Low likelihood, but hard to diagnose.
## How to check
Read `serve` and `stop` together. `KeyInterceptor.swift` is excluded from coverage, and the tests use a plain Mach port rather than a real event tap.
## Acceptance criteria
- The thread checks `stopped` again immediately before entering the run loop (or runs the loop in a `while !stopped` with a bounded `CFRunLoopRunInMode`), so a stop that arrives in the window is never missed.
- A test with the existing `makePort` seam forces `stop()` into that window (for example with a hook between adding the source and running the loop) and shows the thread exits and `released` fires.
- No test depends on wall-clock timing.
Contributor guide
Research direction
Read InterceptorTap.serve and stop in Sources/UttrflowInput/KeyInterceptor.swift:182-213, then inspect the existing makePort test seam. Add a deterministic hook between source registration and CFRunLoopRun, force stop() there, and verify the thread exits and released fires without wall-clock timing.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- macos, swift
- Domain
- desktop, operating-systems, testing
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100