fix: make MemoryUsageRegister thread-safe for concurrent validations#2169
Merged
davidgamez merged 1 commit intoJun 25, 2026
Conversation
MemoryUsageRegister is a process-global singleton with an unsynchronized registry list and a clearRegistry() that ValidationRunner.run() calls at the start of every run. When two validations overlap they append to the same list and one run's clear() wipes the other's in-flight snapshots, corrupting summary.memoryUsageRecords. Validation notices are unaffected because the NoticeContainer is per-run. Both @MemoryMonitor interception points (ValidationRunner.run and GtfsFeedLoader.loadAndValidate) execute on the request thread, so thread-confining the register isolates concurrent runs with no locking and no behavior change for the single-threaded CLI/desktop path. Adds MemoryUsageRegisterTest with a barrier-driven interleaving that reproduces the corruption (expected: A1,A2 but was: B1,A2 on the old singleton) and passes with the fix. Resolves MobilityData#2168. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1uYxGwNbaGQk9MUajBms4
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
MemoryUsageRegisteris a process-global singleton — a single static instance with an unsynchronizedregistrylist — andValidationRunner.run()callsclearRegistry()at the start of every run. When two validations run concurrently in the same JVM, they append to the same list and one run'sclear()wipes the other's in-flight snapshots, sosummary.memoryUsageRecordsis corrupted. Validation notices are unaffected, since theNoticeContaineris per-run.This is the bug described in #2168.
Fix
Thread-confine the register. Both
@MemoryMonitorinterception points —ValidationRunner.runandGtfsFeedLoader.loadAndValidate— execute on the thread that invokesrun(), so giving each thread its own register isolates concurrent validations with no locking and no behavior change for the single-threaded CLI/desktop path.Test
MemoryUsageRegisterTestadds a barrier-driven interleaving that forces the exact corruption (thread B callsclearRegistry()after thread A has registered a snapshot). On the old singleton it fails withexpected: A1,A2 but was: B1,A2; with the fix each thread sees only its own snapshots.Validation against a real concurrent service
We exercised this against an HTTP validator service (a Spring wrapper around a shared
ValidationRunner, as in MobilityData/gtfs-validator-api#1), running 8 concurrent validations of a ~3 MB / ~13k-trip feed:memoryUsageRecords(the single-run baseline is 4) — cross-contaminated.Resolves #2168.