Skip to content

fix(server): Thread-safe allocator for concurrent image loading#169

Merged
dmtrKovalenko merged 1 commit into
dmtrKovalenko:mainfrom
NicoHinderling:04-09-fix_server_thread-safe_allocator_for_concurrent_image_loading
Apr 10, 2026
Merged

fix(server): Thread-safe allocator for concurrent image loading#169
dmtrKovalenko merged 1 commit into
dmtrKovalenko:mainfrom
NicoHinderling:04-09-fix_server_thread-safe_allocator_for_concurrent_image_loading

Conversation

@NicoHinderling
Copy link
Copy Markdown
Contributor

@NicoHinderling NicoHinderling commented Apr 10, 2026

Summary

  • Wrap allocator in std.heap.ThreadSafeAllocator in loadTwoImages and loadTwoImagesFromBuffers — both functions spawn two threads that allocate from the same (non-thread-safe) ArenaAllocator, causing a race condition where overlapping memory regions produce false match: true results
  • Fix thread leak when comp_thread spawn fails after base_thread has already started — join base_thread and clean up before returning
  • Add server regression test that sends 5 sequential diff requests through one server session and verifies none produce false matches

Root cause

loadTwoImages spawns two threads to decode images concurrently, but both threads share the same ArenaAllocator. Zig's arena allocator has no synchronization — two threads can race on end_index, get overlapping memory, and one image's pixel data overwrites the other's. diff() then compares identical data and returns diff_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)

Configuration False matches Rate
No fixes (baseline) 235/1100 21.4%
ThreadSafeAllocator fix 100/1100 9.1%
Both fixes (this PR + dimension fix) 0/1100 0%

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 where compareDifferentLayouts ignores extra pixels when the compare image is larger than the base, addressed in a follow-up PR.

Test plan

  • zig build test-all passes
  • Eliminates all non-deterministic false matches in server mode
  • Review ThreadSafeAllocator lifetime — stack-allocated, threads joined before function returns
  • Review thread-leak fix on spawn failure path

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dmtrKovalenko dmtrKovalenko merged commit 1f693af into dmtrKovalenko:main Apr 10, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants