fix(server): Thread-safe allocator for concurrent image loading#169
Merged
Conversation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This was referenced Apr 10, 2026
dmtrKovalenko
approved these changes
Apr 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
std.heap.ThreadSafeAllocatorinloadTwoImagesandloadTwoImagesFromBuffers— both functions spawn two threads that allocate from the same (non-thread-safe)ArenaAllocator, causing a race condition where overlapping memory regions produce falsematch: trueresultscomp_threadspawn fails afterbase_threadhas already started — join base_thread and clean up before returningRoot cause
loadTwoImagesspawns two threads to decode images concurrently, but both threads share the sameArenaAllocator. Zig's arena allocator has no synchronization — two threads can race onend_index, get overlapping memory, and one image's pixel data overwrites the other's.diff()then compares identical data and returnsdiff_count == 0.The race condition is theoretically present in CLI mode too, but since the process exits immediately after one comparison, the overlapping memory never gets a chance to cause observable damage. In server mode, the corrupted allocations compound across requests.
Isolated test results (server mode, 100 runs × 11 pairs = 1100 comparisons)
The remaining 100/1100 with ThreadSafeAllocator alone are all from
width_change.png— an image pair with different dimensions (200×200 vs 300×200). This is a separate pre-existing bug wherecompareDifferentLayoutsignores extra pixels when the compare image is larger than the base, addressed in a follow-up PR.Test plan
zig build test-allpassesThreadSafeAllocatorlifetime — stack-allocated, threads joined before function returns