The scan consumes the final Waiting/SharedHolding flags of each lock
map: a thread blocked in a lock acquisition emits no Obtain and no other
event, so an unclosed wait at the last known state is a permanent wait
per the event contract. A failed try_lock never emits a wait, so it
cannot fabricate an edge. Locks recorded by clients with inverted
release ordering are skipped entirely, as their holder attribution is
ambiguous.
A thread that holds a shared lock and waits for exclusive cannot
progress while its own shared hold persists; this is reported as a
size-1 cycle.
The differences against the previous renderer are deliberate: the
drawn state is canonical from event 0, HasBlockingLock clears when the
blockers drain, same-state neighbors merge, a waiting thread shows as
holding only on its true acquire, a shared holder waiting for exclusive
draws as blocking, the Blocked and Sharing lists contain only genuinely
blocked roles, and the tooltip holder count follows the cursor position
in the holder-change log.
Capture has serialized lock events since d6f32a083 and 3e3aa80fa, so
the out-of-order reconstruction machinery has nothing left to repair.
Mark now targets the recorded Wait/Obtain event index; the old reverse
scan could walk off the start of the timeline for a thread that never
waited.
InsertLockEvent wraps the uint8 slot index past the 64-entry
per-lock table, misattributing the 65th distinct thread to slot
0. Set the failure instead.
With unlock -> notify order the thread can be preempted by other thread,
for example one that wants to lock the very same lock. This would produce
incorrect ordering of lock release and acquire events.
The deploy job SFTPs the emscripten build using repo secrets
(SERVER/USERNAME/PRIVATE_KEY/REMOTE_PATH) that only exist upstream.
On a fork these are empty, so every push to a fork's master fails
with "remote_path is empty" even though the actual build succeeded.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVBiYgtsB6okBueTwp5swa
Opening and parsing on the browser main thread froze the tab for the
whole load; the file selector path already loads in a worker thread,
so boot now uses the same thread, progress modal, and error dialogs.
The preloaded data package made every ?t= load download embed.tracy
as well. The startup glue now fetches embed.tracy only when no URL is
given, into the same /url.tracy slot, and the boot code has a single
open path.
Version and integrity checks throw from the View constructor, which
was outside the try/catch guarding the file open, so a bad argv trace
terminated the process. Catch and report it there as well.
The compare load thread caught only UnsupportedVersion, so a
corrupted or otherwise unloadable second trace escaped the thread as
std::terminate. Catch the same set as the main trace load and route
it to the existing bad-version dialog.
Exception catching was off by default in the web build, so the
try/catch handlers around trace loading were dead code and any
failure unwound through the page.
Loading on the browser main thread cannot start more than the 8
pre-created workers: beyond that, thread creation waits for the main
thread to return to its event loop, which the load never does.
Decompression threads were only an optimization, so on emscripten
spawn workers for at most 4 streams and decompress the rest on the
loading thread.
glfw 3.5.1 enables OBJC build-wide under CMake 4.x, remapping nfd's
nfd_cocoa.m to a language its scope never loaded; generate then fails on
CMAKE_OBJC_COMPILE_OBJECT. Initialize OBJC at the top level so sibling
scopes get the rules.