Repository navigation
fix(logs): keep an early iOS simulator logs stop from hanging the session - #3892
Conversation
Size Report
Startup median (7 runs, lower is better):
|
|
…sion `xcrun simctl spawn` forwards a stop signal to the in-simulator `log stream` only after the launch completes. A `logs stop` sent during the launch ended simctl, the orphaned stream kept the output pipes open, and the managed wait never settled, so the stop and every later session request hung. Simulator `logs start` now returns once the stream writes its first output, and managed app-log termination releases output pipes that a process outside the child's tree still holds after the child exits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A simulator log stream that never writes output now fails logs start with reason simulator-log-stream-launch-timeout after 10 s instead of holding the session until the client envelope resets the daemon. The process-host test double now models a child that exits while a descendant keeps its pipes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0d3c327 to
5f9f5f2
Compare
|
The fix looks correct at 5f9f5f2. I found nothing that needs a change before merge, but the live evidence is still short of what I can confirm. The reported live runs were at 0d3c327, and I could not confirm they match the rebased patch. No run artifacts are linked. Please link them, or rerun on 5f9f5f2 and show that an early I did not run the tests. I judged that they fail on the base by reading the base code paths. The escaped-descendant test mocks Not blocking: host-kit already owns the rule that a killed command settles when its direct child exits, in createCommandKillSettlement, while the managed host kills the child directly and adds a timer-based stream destroy beside it. Also, the docs line at commands.md:1065 says "On iOS simulators", but the gate in start.ts covers every Apple simulator, so "Apple simulators" is more accurate. You can take or leave both. Could the readiness gate in start.ts stay as it is, and termination abort a per-process AbortController passed to Lint, Typecheck & Package fails on the line cap in |
…log host Drive the escaped-descendant termination test through the real runCmdBackground with a shell whose background loop keeps the inherited pipes open, so it exercises Node's close on stream release. Name the simulator gate after every Apple simulator in the docs and error text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Follow-up for the 2026-10-22 review. Head is now 1. Live evidence at Setup: own iPhone 17 Pro simulator, iOS 27.0, Xcode 27.1 (27A9275),
Request logs are kept only for failed requests. All 12 (the timeout, the 6 cancels, the 3 Readiness signal and quiet predicates. On Xcode 27.1 / iOS 27.0, the first bytes from the in-simulator 2. Host-kit kill settlement I kept the current approach. Concretely:
On test fidelity: I replaced the hand-built-wait test. The new test goes through the real 3. Docs: applied. The line now says "On Apple simulators". I also renamed the timeout message from "iOS simulator log stream…" to "Simulator log stream…" for the same reason. Validation: Raw per-run summary: start → 400 ms → stop → appstate (24 runs)
Raw per-run summary: mid-launch cancels (client killed after delay_ms)Exit 143 means the harness killed the client with SIGTERM. Restart exit 1 in runs 3 and 6 is Run 21 launch-timeout response{"code":"COMMAND_FAILED","message":"Simulator log stream did not start within 10000ms","hint":"Check that the simulator finished booting, then retry logs start.","details":{"reason":"simulator-log-stream-launch-timeout","timeoutMs":10000}}Request log: |
|
The findings from the earlier review are fixed at 872985b, and I found no new problems in the code. I did not run the new escaped-descendant test or any other test. I judged it from the close-only settle path in host-kit at this commit. The live runs are the author's, with inline summaries. I could not re-run them, and no artifacts are linked. The readiness signal (first output byte from the in-simulator log process) is checked only on Xcode 27.1 and iOS 27.0, so older runtimes such as the iOS 26.2 runtime in CI are not covered. The author's run under host load of about 500 on 12 cores had 1 of 24 launches pass the 10 s deadline and fail with the typed reason. That is correct behavior, but the deadline's headroom on loaded CI hosts is not measured. Smoke Tests is still running and has not failed. The iOS smoke route replays |
Brings in #3892, which fixes the iOS simulator logs stop hang. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * origin/main: perf(ios): read each resolved text input through one snapshot (#3882) fix(gate): take the host validation lock for heavy gates (#3900) fix(logs): keep an early iOS simulator logs stop from hanging the session (#3892) fix(apple): clean the runner derived path the runner uses (#3897) test(provider-scenarios): open deadline scenarios without the test IME (#3903) test(host-kit): stub os.homedir for the exec-trace late-close test (#3898)
#3892 fixed the iOS simulator logs stop hang, so the untimed wait before logs-stop is no longer needed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ps/ios-suite: fix(perf): stop the log stream right after it starts perf(ios): read each resolved text input through one snapshot (#3882) fix(gate): take the host validation lock for heavy gates (#3900) fix(logs): keep an early iOS simulator logs stop from hanging the session (#3892) fix(apple): clean the runner derived path the runner uses (#3897) test(provider-scenarios): open deadline scenarios without the test IME (#3903) test(host-kit): stub os.homedir for the exec-trace late-close test (#3898)
* feat(perf): time every iOS command against the fixture app pnpm perf --platform ios now drives the test app through seven scenarios that time each catalog command the iOS simulator serves, including the heavy feed and a raw capture with the XCTest tree backend pinned. The suite records a reason for every command it does not time, and a unit test keeps that partition complete. Each sample keeps the client daemon round trip and a phase profile from its --debug request log: runner commands, host process spawns, snapshot sources, retries, stabilization, and fallbacks. A run writes summary.json, report.md, and the raw daemon and session logs. The iOS Settings tour is removed; the Android tour stays as it was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(perf): compare nightly iOS runs and run the suite every night (#3886) pnpm perf:compare judges one run against earlier runs: by default the seven nights before it, or a chosen run, date, range, or local output. A step's baseline is the median of its nightly medians. A change is flagged only when it is larger than 20%, three times the night-to-night scaled MAD, twice the run's own cv/sqrt(n), and 50 ms; new failures and fixed steps are flagged too. perf-nightly runs the iOS suite at 02:40 UTC again (#1781 A3). Each run uploads perf-ios-summary (90 days) and perf-ios-raw (30 days), and writes the comparison to the job summary with warnings for regressions and new failures. Android stays report-only behind a dispatch input. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(perf): judge nights, samples, and failures soundly; keep logs on interrupt - Rolling baselines take one scheduled run per UTC night (the newest usable one) and page through workflow runs until enough nights are found. Explicit --to/--from references still accept any trigger. - A run measures at least three rounds. A timing verdict needs three successful samples tonight and on each counted baseline night. - Failures get their own verdict. more-failures flags tonight failing more than every baseline night, in count and share; a faster median with worse failures is not an improvement. - The interrupt handler and the normal finish share one finalizer: save logs, stop session and daemon within a deadline, save again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(perf): pin the tree backend on a capture it serves, and share host readers - The forced-tree step ran a raw capture. The runner honors a backend pin only on the regular plan, so the raw plan recovered through private AX and the expectation passed a degraded row. The step is now a regular pinned capture on the home screen, and its expectation requires the pinned backend with the runner's `requested-backend` code. - Perf metadata reads the revision, toolchain, and simulator through the readers of the iOS snapshot benchmark, moved to scripts/lib/bench-host.ts. - host-kit and the daemon client export the phase names that the profiler reads. - summary-fixture.ts becomes summary.fixtures.ts; the nightly header matches 3 rounds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(perf): keep the daemon request phase literal Only scripts read it, so a root export has no production consumer and fails the fallow audit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(perf): stop the log stream right after it starts #3892 fixed the iOS simulator logs stop hang, so the untimed wait before logs-stop is no longer needed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* origin/main: perf(ios): confirm fill focus from the tapped input before the keyboard (#3906) feat(perf): time every iOS command against the fixture app (#3885) fix(ios): stop the XCTest-channel penalty from re-arming itself (#3896) test(android): pin that a child vanishing mid-read reads as gone (#3905) ci: skip device smoke lanes when a PR changes no lane input (#3904) fix(ios): verify deep-tree back on the sole screen (#3894) fix(ios): synthesize long presses and the gesture viewport on deep trees (#3893) perf(ios): read each resolved text input through one snapshot (#3882) fix(gate): take the host validation lock for heavy gates (#3900) fix(logs): keep an early iOS simulator logs stop from hanging the session (#3892) fix(apple): clean the runner derived path the runner uses (#3897) test(provider-scenarios): open deadline scenarios without the test IME (#3903) test(host-kit): stub os.homedir for the exec-trace late-close test (#3898) feat(test-app): add a Bluesky-depth heavy feed screen (#3884)
* feat(test-app): add a Bluesky-depth heavy feed screen A JavaScript-only route that reproduces a heavy React Native home feed: 30 nested containers above a virtualized list of composite posts with image, link, quote, and quote-with-media embeds. Its native accessibility tree is deeper than 65 levels, so XCTest tree capture fails with kAXErrorIllegalArgument and recovers through private AX, as on Bluesky 1.136. Settings links to it with open-heavy-feed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(perf): time every iOS command against the fixture app pnpm perf --platform ios now drives the test app through seven scenarios that time each catalog command the iOS simulator serves, including the heavy feed and a raw capture with the XCTest tree backend pinned. The suite records a reason for every command it does not time, and a unit test keeps that partition complete. Each sample keeps the client daemon round trip and a phase profile from its --debug request log: runner commands, host process spawns, snapshot sources, retries, stabilization, and fallbacks. A run writes summary.json, report.md, and the raw daemon and session logs. The iOS Settings tour is removed; the Android tour stays as it was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(perf): compare nightly iOS runs and run the suite every night (#3886) pnpm perf:compare judges one run against earlier runs: by default the seven nights before it, or a chosen run, date, range, or local output. A step's baseline is the median of its nightly medians. A change is flagged only when it is larger than 20%, three times the night-to-night scaled MAD, twice the run's own cv/sqrt(n), and 50 ms; new failures and fixed steps are flagged too. perf-nightly runs the iOS suite at 02:40 UTC again (#1781 A3). Each run uploads perf-ios-summary (90 days) and perf-ios-raw (30 days), and writes the comparison to the job summary with warnings for regressions and new failures. Android stays report-only behind a dispatch input. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(perf): judge nights, samples, and failures soundly; keep logs on interrupt - Rolling baselines take one scheduled run per UTC night (the newest usable one) and page through workflow runs until enough nights are found. Explicit --to/--from references still accept any trigger. - A run measures at least three rounds. A timing verdict needs three successful samples tonight and on each counted baseline night. - Failures get their own verdict. more-failures flags tonight failing more than every baseline night, in count and share; a faster median with worse failures is not an improvement. - The interrupt handler and the normal finish share one finalizer: save logs, stop session and daemon within a deadline, save again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(perf): time every Android command against the fixture app Run iOS and Android from one shared suite: platform deltas are data on each step, and one harness owns rounds, sessions, and daemon restarts. The heavy-feed scenario times the Android helper's one-shot instrumentation capture by restarting the daemon with the helper session off. Request profiles name the helper transport that answered, adb spawns, and helper gestures. The nightly runs both platforms and compares each against its own nights; the Settings tour is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(perf): keep Android clipboard and restore failures out of timings The API 35 and 36 emulator images ship no shell clipboard service, so clipboard is an Android exclusion. A daemon restart that reopens the session clears the stuck flag, and one that cannot reopen it skips the rest of the scenario as restore_failed. The one-shot step warms the restarted daemon's helper install check untimed. latest and date references resolve to the newest run with the platform's artifact. The tour-only --n alias is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(perf): time every iOS command against the fixture app pnpm perf --platform ios now drives the test app through seven scenarios that time each catalog command the iOS simulator serves, including the heavy feed and a raw capture with the XCTest tree backend pinned. The suite records a reason for every command it does not time, and a unit test keeps that partition complete. Each sample keeps the client daemon round trip and a phase profile from its --debug request log: runner commands, host process spawns, snapshot sources, retries, stabilization, and fallbacks. A run writes summary.json, report.md, and the raw daemon and session logs. The iOS Settings tour is removed; the Android tour stays as it was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(perf): compare nightly iOS runs and run the suite every night (#3886) pnpm perf:compare judges one run against earlier runs: by default the seven nights before it, or a chosen run, date, range, or local output. A step's baseline is the median of its nightly medians. A change is flagged only when it is larger than 20%, three times the night-to-night scaled MAD, twice the run's own cv/sqrt(n), and 50 ms; new failures and fixed steps are flagged too. perf-nightly runs the iOS suite at 02:40 UTC again (#1781 A3). Each run uploads perf-ios-summary (90 days) and perf-ios-raw (30 days), and writes the comparison to the job summary with warnings for regressions and new failures. Android stays report-only behind a dispatch input. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(perf): judge nights, samples, and failures soundly; keep logs on interrupt - Rolling baselines take one scheduled run per UTC night (the newest usable one) and page through workflow runs until enough nights are found. Explicit --to/--from references still accept any trigger. - A run measures at least three rounds. A timing verdict needs three successful samples tonight and on each counted baseline night. - Failures get their own verdict. more-failures flags tonight failing more than every baseline night, in count and share; a faster median with worse failures is not an improvement. - The interrupt handler and the normal finish share one finalizer: save logs, stop session and daemon within a deadline, save again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(perf): pin the tree backend on a capture it serves, and share host readers - The forced-tree step ran a raw capture. The runner honors a backend pin only on the regular plan, so the raw plan recovered through private AX and the expectation passed a degraded row. The step is now a regular pinned capture on the home screen, and its expectation requires the pinned backend with the runner's `requested-backend` code. - Perf metadata reads the revision, toolchain, and simulator through the readers of the iOS snapshot benchmark, moved to scripts/lib/bench-host.ts. - host-kit and the daemon client export the phase names that the profiler reads. - summary-fixture.ts becomes summary.fixtures.ts; the nightly header matches 3 rounds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(perf): read Android phase names from their emitters, and document the pinned capture - platform-android exports the helper capture and gesture phase names through its mechanics entry; the profiler and its test import them. - The README's snapshot-path section and parity table name the iOS pinned capture on home-reads and the reason code it must carry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(perf): name the failed restore when restarting the daemon, and fix the local compare example Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(perf): keep the daemon request phase literal Only scripts read it, so a root export has no production consumer and fails the fallow audit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(perf): stop the log stream right after it starts #3892 fixed the iOS simulator logs stop hang, so the untimed wait before logs-stop is no longer needed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(android): record the answering helper transport on its capture timer The snapshot helper's capture timers now name the transport they drove, and the session timer records whether the session answered. profile.ts reads those fields per event instead of inferring a silent session from event order, and no longer re-spells the producer names platform-android owns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Summary
On an Apple simulator, a
logs stopsent ~400 ms afterlogs startcould hang the session (found by #3885).Root cause:
simctl spawn … log streamforwards a stop signal only after the in-simulatorlogprocess has launched. An earlier SIGINT ended simctl, and the orphanedlogprocess kept the output pipes open, so the managed app-logwaitnever settled.logs startreturns once the stream writes output. If that takes more than 10 s, it fails withdetails.reason: "simulator-log-stream-launch-timeout".6 files plus a
commands.mdnote.Validation
Head
872985b93d.872985b93d(iOS 27.0 sim, Xcode 27.1, host load 380–930 on 12 cores), see this comment:appstate: 24 runs. Stop never hung: 0.8 / 1.8 / 6.2 s (min/median/max). 0 in-sim and 0 host leftovers.0d3c327, baseb74fcebdc1hung 2 times in 10 runs.check:affected --run --head 872985b93d: format, lint, and typecheck passed. Vitest passed 1299/1300. Its one failure (apple-platform-output-guardrecordtimeout) also fails on maine3ed027a1dat load ~900. CI Integration Tests passes.🤖 Generated with Claude Code