uttrflow / uttrflow/uttrflow-swift

KeyInterceptor's stop-before-run race relies on run loop behaviour rather than an explicit handshake

Open
#487 0 comments 0 reactions 0 assignees View on GitHub
area:ai-suggestions area:insertion enhancement P2
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.