diff --git a/app/src/main/java/net/kollnig/missioncontrol/data/TrackerBlocklist.java b/app/src/main/java/net/kollnig/missioncontrol/data/TrackerBlocklist.java index de90b806..3d47587a 100644 --- a/app/src/main/java/net/kollnig/missioncontrol/data/TrackerBlocklist.java +++ b/app/src/main/java/net/kollnig/missioncontrol/data/TrackerBlocklist.java @@ -58,7 +58,8 @@ private TrackerBlocklist(Context c) { * @return The current instance of the TrackerBlocklist, if none, a new instance * is created. */ - public static TrackerBlocklist getInstance(Context c) { + // Called from both native packet threads and UI threads. + public static synchronized TrackerBlocklist getInstance(Context c) { if (instance == null) instance = new TrackerBlocklist(c); @@ -85,7 +86,7 @@ public static String getBlockingKey(Tracker t) { * * @param c Context */ - public void loadSettings(Context c) { + public synchronized void loadSettings(Context c) { SharedPreferences prefs = c.getSharedPreferences(PREF_BLOCKLIST, Context.MODE_PRIVATE); Set set = prefs.getStringSet(SHARED_PREFS_BLOCKLIST_APPS_KEY, null); PackageUidResolver resolver = new PackageUidResolver() { @@ -226,14 +227,14 @@ public Set getBlocklist() { * @param uid Uid of the app * @return Information about what specific trackers are blocked */ - public Set getSubset(int uid) { + public synchronized Set getSubset(int uid) { return blockmap.get(uid); } /** * Completely clear blocklist. */ - public void clear() { + public synchronized void clear() { blockmap.clear(); } @@ -242,7 +243,7 @@ public void clear() { * * @param uid Uid of app */ - public void clear(int uid) { + public synchronized void clear(int uid) { blockmap.remove(uid); } @@ -303,7 +304,7 @@ public synchronized void unblock(int uid, Tracker t) { * @param key Key of the tracker * @return Whether access to this tracker is blocked */ - public boolean blocked(int uid, String key) { + public synchronized boolean blocked(int uid, String key) { Set trackers = this.getSubset(uid); if (trackers == null) { return true; @@ -319,7 +320,7 @@ public boolean blocked(int uid, String key) { * @param t Tracker * @return Whether access to this tracker is blocked */ - public boolean blockedTracker(int uid, Tracker t) { + public synchronized boolean blockedTracker(int uid, Tracker t) { return blocked(uid, t.category) && blocked(uid, getBlockingKey(t)); } diff --git a/app/src/test/java/net/kollnig/missioncontrol/data/TrackerBlocklistTest.java b/app/src/test/java/net/kollnig/missioncontrol/data/TrackerBlocklistTest.java index 08f7295c..4e282bbb 100644 --- a/app/src/test/java/net/kollnig/missioncontrol/data/TrackerBlocklistTest.java +++ b/app/src/test/java/net/kollnig/missioncontrol/data/TrackerBlocklistTest.java @@ -21,6 +21,10 @@ import org.junit.Before; import org.junit.Test; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + public class TrackerBlocklistTest { private static final int UID = 1001; @@ -202,4 +206,45 @@ public void blockedTrackerRequiresBothCategoryAndKeyBlocked() { blocklist.block(UID, tracker); assertTrue(blocklist.blockedTracker(UID, tracker)); } + + @Test + public void concurrentReadersAndWritersDoNotThrow() throws Exception { + TrackerBlocklist blocklist = TrackerBlocklist.getInstance(null); + int uidCount = 8; + for (int uid = 0; uid < uidCount; uid++) + blocklist.ensureDefaults(uid, false); + + final int iterations = 1000; + List errors = Collections.synchronizedList(new ArrayList<>()); + + Runnable writer = () -> { + try { + for (int i = 0; i < iterations; i++) { + blocklist.applyStrictModeToAll(i % 2 == 0); + blocklist.unblock(0, "Advertising | Writer"); + blocklist.block(0, "Advertising | Writer"); + } + } catch (Throwable t) { + errors.add(t); + } + }; + Runnable reader = () -> { + try { + for (int i = 0; i < iterations; i++) + for (int uid = 0; uid < uidCount; uid++) + blocklist.blocked(uid, "Content"); + } catch (Throwable t) { + errors.add(t); + } + }; + + Thread[] threads = {new Thread(writer), new Thread(writer), + new Thread(reader), new Thread(reader)}; + for (Thread thread : threads) + thread.start(); + for (Thread thread : threads) + thread.join(30000); + + assertTrue(errors.toString(), errors.isEmpty()); + } }