fix(macos): avoid camera-permission deadlock when opening off the main thread#56
fix(macos): avoid camera-permission deadlock when opening off the main thread#56LeeGoDamn wants to merge 6 commits into
Conversation
…n thread ProviderApple::open() requested camera authorization by dispatching the request onto the main dispatch queue for non-main-thread callers, then blocking on a semaphore. When nothing services the main queue -- e.g. a ccap::Provider opened from a worker thread in a process with no CFRunLoop on its main thread (a Node.js/Electron addon, a head-less multi-threaded service) -- the dispatched block never runs, so the permission request is never even issued and open() hangs forever. Main-thread callers and apps with a running run loop were unaffected, which is why this stayed dormant. requestAccessForMediaType: may be called from any thread and delivers its completion on an internal queue, so the main-queue hop is unnecessary. Extract the "start an async request and block until it completes" logic into ccap::runBlockingAsyncRequest() (src/ccap_apple_async.h), which runs the request on the calling thread and waits on a portable condition variable. The blocking "wait until the user decides" behavior is preserved; only the deadlock-prone main-queue dispatch is removed. Add tests/test_apple_permission.cpp: a deterministic regression test that drives the real helper with a simulated async request (a countdown firing the completion from a background thread) and asserts the wait does not deadlock when invoked off the main thread with no run loop. It builds into the existing ccap_convert_test aggregate, so it runs in the macOS CI "Run Full Test Suite" job (./run_tests.sh --functional); it is an empty translation unit elsewhere. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Warning Review limit reached
More reviews will be available in 19 minutes and 7 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more credits in the billing tab to continue. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
WalkthroughAdds an Apple-only blocking helper for callback-based async requests, uses it for camera permission access on macOS, adds regression coverage for synchronous and asynchronous completion, and updates Windows build jobs and generator settings. ChangesApple Async Blocking Helper
Windows Build Workflow Update
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/test_apple_permission.cpp`:
- Around line 53-73: The timeout helper in the permission test can still hang
because the inline body path bypasses any real watchdog and the detached worker
keeps a reference to donePromise after return. Update the helper around the body
lambda and worker thread handling so the “main thread” case is exercised under
an external watchdog/subprocess rather than directly inline, and move the
promise/shared state to heap-managed storage before any detach so late
completion cannot access a freed reference.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 68d40a48-1016-433a-891a-46a752deddc8
📒 Files selected for processing (4)
src/ccap_apple_async.hsrc/ccap_imp_apple.mmtests/CMakeLists.txttests/test_apple_permission.cpp
Address review feedback on the regression test harness: - Always run the code under test on a worker thread with the watchdog on the calling thread, so a deadlock regression fails via a clean timeout instead of hanging the test binary (the previous inline main-thread path bypassed it). - Keep the completion promise on the heap (shared_ptr) shared with the worker, so a late completion after a timeout/detach cannot touch freed stack state. - Replace the redundant main-thread sanity case (the helper is thread-agnostic) with a synchronous-completion case that guards against a missed wakeup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…image windows-latest now resolves to windows-2025-vs2026, which ships Visual Studio 2026 (v18) only. Two breakages resulted, red on every PR regardless of content: - The VS2022 jobs configure with -G "Visual Studio 17 2022", which can no longer find a VS instance. Pin them to runs-on: windows-2022 (still ships VS 2022); VS 2026 is covered by the dedicated build-vs2026 job. - The VS2026 job forced -T v144, which makes MSBuild fail to resolve VCTargetsPath on the VS 2026 image. Drop the toolset override and let CMake use the default toolset that ships with the "Visual Studio 18 2026" generator. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The VS2026 shared-library linking test searched for vcvars64.bat under
"Microsoft Visual Studio\2026", but VS 2026 installs under a version folder
("Microsoft Visual Studio\18\Enterprise"). The find returned nothing, so the
step fell through and exited 1 even though the DLL built fine. Search the whole
Visual Studio directory instead, matching the VS2022 linking test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CurrentTime is the wall-clock playback position, so grabbing buffered frames faster/slower than real-time (common on shared CI runners) makes (time2 - time1) deviate from 5/frameRate in both directions. The previous symmetric +/-50% EXPECT_NEAR failed (near-)consistently on the windows-2022 runner while passing on windows-2025. Keep the forward-progress assertion (EXPECT_GT) and replace the brittle tolerance with a generous upper bound; deterministic progression is already covered by GetCurrentFrameIndexProgression. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The VS2026 configure step used "mkdir -p" under PowerShell, where mkdir maps to New-Item, which errors when the directory already exists. On a build-cache hit the restored build/<config> directory is present, so configure failed intermittently (cache miss passed, cache hit failed). Use New-Item -ItemType Directory -Force, matching the VS2022 job. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Problem
ProviderApple::open()requested camera authorization by dispatching the request onto the main dispatch queue for non-main-thread callers, then blocking on a semaphore (DISPATCH_TIME_FOREVER):When nothing is servicing the main queue — e.g. a
ccap::Provideropened from a worker thread in a process with noCFRunLoopon its main thread (a Node.js/Electron addon, a head-less multi-threaded service) — the dispatched block never runs, so the permission request is never even issued andopen()hangs forever. It is not "waiting for the user to decide"; the prompt is never shown, so no progress is possible.This stayed dormant because the shipping paths don't hit it:
elsebranch — no dispatch, no deadlock.It bites exactly the off-main-thread + no-run-loop context, which is what a native Node binding (libuv worker thread, no CFRunLoop) creates — relevant to where this library is headed.
Fix
requestAccessForMediaType:may be called from any thread and delivers its completion on an internal queue, so the main-queue hop is unnecessary. The "start an async request and block until it completes" logic is extracted intoccap::runBlockingAsyncRequest()(src/ccap_apple_async.h), which runs the request on the calling thread and waits on a portablestd::condition_variable.The blocking "wait until the user decides" behavior is preserved (the wait still blocks indefinitely for the user's response) — only the deadlock-prone main-queue dispatch is removed.
Test (runs in CI)
tests/test_apple_permission.cppis a deterministic regression test that drives the real helper with a simulated async request — a countdown that fires the completion from a background thread, exactly howrequestAccessForMediaType:delivers its completion off the caller's run loop. No camera required.ccap_convert_testaggregate, so it runs in the macOS "Run Full Test Suite" CI job via./run_tests.sh --functional.Verified locally (Apple toolchain):
libccap.acompiles with the change (ARC)ccap_convert_testbuilds with the new testAppleCameraPermission.*run & pass under ASANScope
macOS only. No public API change. Behavior for main-thread callers is unchanged.
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Chores