Commit d187600
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
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>
* test(macos): harden permission deadlock test against hangs and UAF
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>
* ci(windows): fix VS builds after windows-latest moved to the VS 2026 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>
* ci(windows): fix VS2026 shared-link test vcvars path
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>
* test(playback): make GetCurrentTimeProgression robust to CI timing
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>
* ci(windows): make VS2026 configure idempotent for build-cache hits
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>
* fix(macos): notify condition_variable under lock; address review findings
- ccap_apple_async.h: move cv.notify_one() inside the locked scope. mutex/cv/
finished are stack-locals the waiting thread destroys on return, so notifying
after unlock risks a use-after-free if the waiter wakes (e.g. spuriously),
sees finished == true, and returns before notify_one() runs. Holding the lock
blocks the waiter from re-acquiring it (and thus returning) until notify
completes.
- test_file_playback.cpp: assert FrameRate > 0 so the loosened upper bound
cannot go vacuous (inf/NaN) on a regression that zeroes the frame rate.
- test_apple_permission.cpp: reframe the docstring -- it pins the helper's
contract (what open() delegates to), not an end-to-end open()/AVFoundation
test, which needs a real camera/TCC and cannot run deterministically in CI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(macos): drop the C++ wait helper, keep the minimal GCD fix
The deadlock was caused solely by dispatch_async'ing the permission request onto
the main queue, which never runs when no run loop services it. The fix is just to
delete that bounce and call requestAccessForMediaType: directly on the calling
thread, keeping the original dispatch_semaphore wait.
The earlier ccap_apple_async.h helper (std::mutex/condition_variable) and its unit
test were introduced only to make the path portable-C++-testable, but the file was
macOS-only (portability was moot), the cv-on-the-stack rewrite introduced its own
destroy-after-notify UAF, and the test never exercised ProviderApple::open() -- it
tested the invented helper, not the real code path. GCD's dispatch_semaphore is
simpler and, because the completion block retains the semaphore, has no destroy race.
Removes src/ccap_apple_async.h and tests/test_apple_permission.cpp.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: wysaid <this@wysaid.org>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>1 parent 0d1a0f7 commit d187600
3 files changed
Lines changed: 39 additions & 27 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
22 | | - | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
23 | 26 | | |
24 | 27 | | |
25 | 28 | | |
| |||
311 | 314 | | |
312 | 315 | | |
313 | 316 | | |
314 | | - | |
315 | | - | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
316 | 321 | | |
317 | 322 | | |
318 | | - | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
319 | 326 | | |
320 | | - | |
| 327 | + | |
321 | 328 | | |
322 | | - | |
323 | | - | |
| 329 | + | |
| 330 | + | |
324 | 331 | | |
325 | 332 | | |
326 | 333 | | |
| |||
391 | 398 | | |
392 | 399 | | |
393 | 400 | | |
394 | | - | |
395 | | - | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
396 | 405 | | |
397 | 406 | | |
398 | 407 | | |
| |||
658 | 667 | | |
659 | 668 | | |
660 | 669 | | |
661 | | - | |
| 670 | + | |
| 671 | + | |
662 | 672 | | |
663 | 673 | | |
664 | 674 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
256 | 256 | | |
257 | 257 | | |
258 | 258 | | |
259 | | - | |
260 | | - | |
261 | | - | |
262 | | - | |
263 | | - | |
264 | | - | |
265 | | - | |
266 | | - | |
267 | | - | |
268 | | - | |
269 | | - | |
270 | | - | |
271 | | - | |
272 | | - | |
273 | 259 | | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
274 | 270 | | |
275 | 271 | | |
276 | 272 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
716 | 716 | | |
717 | 717 | | |
718 | 718 | | |
| 719 | + | |
719 | 720 | | |
720 | 721 | | |
721 | | - | |
722 | | - | |
723 | | - | |
| 722 | + | |
| 723 | + | |
| 724 | + | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
| 728 | + | |
| 729 | + | |
724 | 730 | | |
725 | 731 | | |
726 | 732 | | |
| |||
0 commit comments