Skip to content

Synchronize TrackerBlocklist access across packet and UI threads - #750

Merged
kasnder merged 1 commit into
masterfrom
fix/tracker-blocklist-races
Aug 22, 2026
Merged

Synchronize TrackerBlocklist access across packet and UI threads#750
kasnder merged 1 commit into
masterfrom
fix/tracker-blocklist-races

Conversation

@kasnder

@kasnder kasnder commented Aug 22, 2026

Copy link
Copy Markdown
Member

Problem

TrackerBlocklist's read path (blocked/blockedTracker/getSubset) ran unsynchronized on native JNI packet threads while writers on the UI thread mutated the same plain HashSet values, so concurrent reads could hit ConcurrentModificationException or torn state inside the VPN packet path — where an exception propagates through JNI and can tear down the VPN service. The lazy singleton in getInstance was also not synchronized, so concurrent first calls from packet and UI threads could create two instances, with the orphan serving stale blocking decisions indefinitely. loadSettings additionally rebuilt blockmap.clear()+rebuild while readers could be mid-iteration.

Fix

  • Made getInstance a static synchronized method
  • Made loadSettings, getSubset, blocked, blockedTracker, clear(), clear(int) synchronized instance methods (joining the already-synchronized writers)
  • No logic, defaults, migration-string, or return-value changes — mutual exclusion only
  • Added a JVM unit test hammering readers vs strict-mode/block writers concurrently, asserting no exceptions

Test plan

  • ./gradlew :app:compileGithubDebugJavaWithJavac -q → exit 0
  • ./gradlew :app:testGithubDebugUnitTest -q → exit 0 (full suite green)

Readers (blocked/blockedTracker/getSubset) ran unsynchronized on native
JNI packet threads while writers on the UI thread mutated the same plain
HashSet values, risking ConcurrentModificationException inside the VPN
packet path and torn reads. The lazy singleton in getInstance could also
split-brain into two instances under concurrent first calls, leaving an
orphan instance serving stale decisions. Make all entry points mutually
exclusive; no behavior change.
@kasnder
kasnder merged commit ab49e7a into master Aug 22, 2026
2 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.

1 participant