From ece73317f842002995fca7a4e48c6d8bf7b0e7e4 Mon Sep 17 00:00:00 2001 From: harshraj22 Date: Sat, 30 Aug 2025 20:25:55 +0530 Subject: [PATCH 1/6] Simplifies cache hit collector metric class --- .../src/main/java/com/example/Main.java | 2 +- .../outbound/CacheHitMetricCollector.java | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/lld/flipCache/src/main/java/com/example/Main.java b/lld/flipCache/src/main/java/com/example/Main.java index 6f65a58..9999df8 100644 --- a/lld/flipCache/src/main/java/com/example/Main.java +++ b/lld/flipCache/src/main/java/com/example/Main.java @@ -19,7 +19,7 @@ public static void main(String[] args) { EvictionPolicy evictionPolicy = new SimpleEvictionPolicy<>(); FlipCache flipCache = new FlipCache<>(maxSizeOfCache, dataSource, evictionPolicy); - CacheHook cacheHitMetricCollector = new CacheHitMetricCollector<>(keys.getFirst()); + CacheHook cacheHitMetricCollector = new CacheHitMetricCollector<>(); flipCache.registerHook(cacheHitMetricCollector); flipCache.setCache(keys.get(0), "ValueA"); diff --git a/lld/flipCache/src/main/java/com/example/adaptor/outbound/CacheHitMetricCollector.java b/lld/flipCache/src/main/java/com/example/adaptor/outbound/CacheHitMetricCollector.java index 8563c03..a090bd9 100644 --- a/lld/flipCache/src/main/java/com/example/adaptor/outbound/CacheHitMetricCollector.java +++ b/lld/flipCache/src/main/java/com/example/adaptor/outbound/CacheHitMetricCollector.java @@ -3,22 +3,20 @@ import com.example.application.models.Event; import com.example.application.ports.outbound.CacheHook; -public class CacheHitMetricCollector implements CacheHook { - private int cacheHitCount = 0; - private final K key; +import java.util.HashMap; +import java.util.Map; - public CacheHitMetricCollector(K key) { - this.key = key; - } +public class CacheHitMetricCollector implements CacheHook { + private final Map hitCounts = new HashMap<>(); - public int getCacheHitCount() { - return cacheHitCount; + public int getCacheHitCount(K key) { + return hitCounts.getOrDefault(key, 0); } @Override public void onEvent(Event event, K key, V value) { - if (event == Event.HIT && key.equals(this.key)) { - cacheHitCount += 1; + if (event == Event.HIT) { + hitCounts.put(key, getCacheHitCount(key) + 1); } } From 003a4ce15441ab555c6e12eaa4ef560cc46c4507 Mon Sep 17 00:00:00 2001 From: harshraj22 Date: Sat, 30 Aug 2025 20:52:21 +0530 Subject: [PATCH 2/6] Adds code to parallel use the flip cache and identify the issue with synchronization --- .../src/main/java/com/example/Main.java | 79 ++++++++++++++++++- .../adaptor/outbound/InMemoryDataSource.java | 1 + .../application/service/FlipCache.java | 3 +- 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/lld/flipCache/src/main/java/com/example/Main.java b/lld/flipCache/src/main/java/com/example/Main.java index 9999df8..3981ff1 100644 --- a/lld/flipCache/src/main/java/com/example/Main.java +++ b/lld/flipCache/src/main/java/com/example/Main.java @@ -8,10 +8,87 @@ import com.example.application.ports.outbound.EvictionPolicy; import com.example.application.service.FlipCache; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; public class Main { public static void main(String[] args) { + try { + multiThreadExecutor(); + } catch (ExecutionException | InterruptedException e) { + throw new RuntimeException(e); + } + } + + public static void multiThreadExecutor() throws ExecutionException, InterruptedException { + // Use fixed thread pool for better thread safety testing + ExecutorService executorService = Executors.newFixedThreadPool(10); + try { + // Test your cache with concurrent operations + testCacheThreadSafety(executorService); + } finally { + executorService.shutdown(); + if (!executorService.awaitTermination(10, java.util.concurrent.TimeUnit.SECONDS)) { + executorService.shutdownNow(); + } + } + } + + private static void testCacheThreadSafety(ExecutorService executorService) throws ExecutionException, InterruptedException { + // Setup cache + DataSource dataSource = new InMemoryDataSource<>(); + EvictionPolicy evictionPolicy = new SimpleEvictionPolicy<>(); + FlipCache flipCache = new FlipCache<>(5, dataSource, evictionPolicy); + + CacheHook cacheHitMetricCollector = new CacheHitMetricCollector<>(); + flipCache.registerHook(cacheHitMetricCollector); + + // Submit multiple concurrent tasks + List> futures = new ArrayList<>(); + + // Concurrent writes + for (int i = 0; i < 20; i++) { + final int threadId = i; + futures.add(executorService.submit(() -> { + String key = "key" + (threadId % 10); + String value = "value" + threadId; + flipCache.setCache(key, value); + System.out.println("Thread " + threadId + " set: " + key + " = " + value); + })); + } + + // Concurrent reads + for (int i = 0; i < 20; i++) { + final int threadId = i; + futures.add(executorService.submit(() -> { + String key = "key" + (threadId % 10); + String value = flipCache.getCache(key); + System.out.println("Thread " + threadId + " got: " + key + " = " + value); + })); + } + + // Wait for all tasks to complete + for (Future future : futures) { + future.get(); + } + + System.out.println("All concurrent operations completed"); + System.out.println("Final cache state and metrics:"); + + // Check final state + for (int i = 0; i < 10; i++) { + String key = "key" + i; + String value = flipCache.getCache(key); + int hitCount = ((CacheHitMetricCollector) cacheHitMetricCollector).getCacheHitCount(key); + System.out.println(key + " = " + value + ", hits: " + hitCount); + } + } + + public static void singleThreadExecutor() { List keys = List.of("A", "B", "C"); int maxSizeOfCache = 2; @@ -30,6 +107,6 @@ public static void main(String[] args) { key -> System.out.println(String.format("Got Value: {%s} from cache for key: {%s}", flipCache.getCache(key), key)) ); -// System.out.println(String.format("Metric collected: Number of hits: {%s}",)); + System.out.println(String.format("Metric collected: Number of hits: {%s}", ((CacheHitMetricCollector)cacheHitMetricCollector).getCacheHitCount("A"))); } } \ No newline at end of file diff --git a/lld/flipCache/src/main/java/com/example/adaptor/outbound/InMemoryDataSource.java b/lld/flipCache/src/main/java/com/example/adaptor/outbound/InMemoryDataSource.java index 7753522..2c304a6 100644 --- a/lld/flipCache/src/main/java/com/example/adaptor/outbound/InMemoryDataSource.java +++ b/lld/flipCache/src/main/java/com/example/adaptor/outbound/InMemoryDataSource.java @@ -15,6 +15,7 @@ public InMemoryDataSource() { @Override public V persist(K key, V value) { data.put(key, value); + System.out.println("Persisted key: " + key + ", value: " + value + " in InMemoryDataSource. Size: " + data.size()); return value; } diff --git a/lld/flipCache/src/main/java/com/example/application/service/FlipCache.java b/lld/flipCache/src/main/java/com/example/application/service/FlipCache.java index 9936814..e79031a 100644 --- a/lld/flipCache/src/main/java/com/example/application/service/FlipCache.java +++ b/lld/flipCache/src/main/java/com/example/application/service/FlipCache.java @@ -31,9 +31,8 @@ private void triggerHooks(Event event, K key, V value) { ); } - public CacheHook registerHook(CacheHook cacheHook) { + public void registerHook(CacheHook cacheHook) { this.hooks.add(cacheHook); - return cacheHook; } @Override From e98e872f5760c963949fecb52ad32daccc7936e0 Mon Sep 17 00:00:00 2001 From: harshraj22 Date: Sat, 30 Aug 2025 21:16:39 +0530 Subject: [PATCH 3/6] Adds thread safety to the classes --- lld/flipCache/src/main/java/com/example/Main.java | 10 +++++++--- .../adaptor/outbound/CacheHitMetricCollector.java | 3 ++- .../adaptor/outbound/InMemoryDataSource.java | 13 ++++++++++--- .../adaptor/outbound/SimpleEvictionPolicy.java | 3 ++- .../com/example/application/service/FlipCache.java | 13 +++++++------ 5 files changed, 28 insertions(+), 14 deletions(-) diff --git a/lld/flipCache/src/main/java/com/example/Main.java b/lld/flipCache/src/main/java/com/example/Main.java index 3981ff1..9dfb3b7 100644 --- a/lld/flipCache/src/main/java/com/example/Main.java +++ b/lld/flipCache/src/main/java/com/example/Main.java @@ -28,8 +28,11 @@ public static void multiThreadExecutor() throws ExecutionException, InterruptedE // Use fixed thread pool for better thread safety testing ExecutorService executorService = Executors.newFixedThreadPool(10); try { - // Test your cache with concurrent operations + // add code to measure how long it takes to execute the test + long startTime = System.currentTimeMillis(); testCacheThreadSafety(executorService); + long endTime = System.currentTimeMillis(); + System.out.println("======Test completed in " + (endTime - startTime) + " ms ======="); } finally { executorService.shutdown(); if (!executorService.awaitTermination(10, java.util.concurrent.TimeUnit.SECONDS)) { @@ -51,7 +54,8 @@ private static void testCacheThreadSafety(ExecutorService executorService) throw List> futures = new ArrayList<>(); // Concurrent writes - for (int i = 0; i < 20; i++) { + int concurrentHitCount = 20_000; + for (int i = 0; i < concurrentHitCount; i++) { final int threadId = i; futures.add(executorService.submit(() -> { String key = "key" + (threadId % 10); @@ -62,7 +66,7 @@ private static void testCacheThreadSafety(ExecutorService executorService) throw } // Concurrent reads - for (int i = 0; i < 20; i++) { + for (int i = 0; i < concurrentHitCount; i++) { final int threadId = i; futures.add(executorService.submit(() -> { String key = "key" + (threadId % 10); diff --git a/lld/flipCache/src/main/java/com/example/adaptor/outbound/CacheHitMetricCollector.java b/lld/flipCache/src/main/java/com/example/adaptor/outbound/CacheHitMetricCollector.java index a090bd9..ceed7f2 100644 --- a/lld/flipCache/src/main/java/com/example/adaptor/outbound/CacheHitMetricCollector.java +++ b/lld/flipCache/src/main/java/com/example/adaptor/outbound/CacheHitMetricCollector.java @@ -5,9 +5,10 @@ import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; public class CacheHitMetricCollector implements CacheHook { - private final Map hitCounts = new HashMap<>(); + private final Map hitCounts = new ConcurrentHashMap<>(); public int getCacheHitCount(K key) { return hitCounts.getOrDefault(key, 0); diff --git a/lld/flipCache/src/main/java/com/example/adaptor/outbound/InMemoryDataSource.java b/lld/flipCache/src/main/java/com/example/adaptor/outbound/InMemoryDataSource.java index 2c304a6..80dcea4 100644 --- a/lld/flipCache/src/main/java/com/example/adaptor/outbound/InMemoryDataSource.java +++ b/lld/flipCache/src/main/java/com/example/adaptor/outbound/InMemoryDataSource.java @@ -2,20 +2,27 @@ import com.example.application.ports.outbound.DataSource; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; public class InMemoryDataSource implements DataSource { private final Map data; public InMemoryDataSource() { - this.data = new HashMap<>(); + this.data = new ConcurrentHashMap<>(); } @Override - public V persist(K key, V value) { + public synchronized V persist(K key, V value) { data.put(key, value); + /* + For Testing multi-threaded access and exception handling System.out.println("Persisted key: " + key + ", value: " + value + " in InMemoryDataSource. Size: " + data.size()); + if (data.size() > 5) { + throw new RuntimeException("InMemoryDataSource capacity exceeded"); + } + */ + return value; } diff --git a/lld/flipCache/src/main/java/com/example/adaptor/outbound/SimpleEvictionPolicy.java b/lld/flipCache/src/main/java/com/example/adaptor/outbound/SimpleEvictionPolicy.java index eb11687..d746a8a 100644 --- a/lld/flipCache/src/main/java/com/example/adaptor/outbound/SimpleEvictionPolicy.java +++ b/lld/flipCache/src/main/java/com/example/adaptor/outbound/SimpleEvictionPolicy.java @@ -4,12 +4,13 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; public class SimpleEvictionPolicy implements EvictionPolicy { private final List keys; public SimpleEvictionPolicy() { - this.keys = new ArrayList<>(); + this.keys = new CopyOnWriteArrayList<>(); } @Override diff --git a/lld/flipCache/src/main/java/com/example/application/service/FlipCache.java b/lld/flipCache/src/main/java/com/example/application/service/FlipCache.java index e79031a..80b6148 100644 --- a/lld/flipCache/src/main/java/com/example/application/service/FlipCache.java +++ b/lld/flipCache/src/main/java/com/example/application/service/FlipCache.java @@ -9,10 +9,11 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; public class FlipCache implements GetCache, SetCache { private final int maxSize; - private int currentSize; + private final AtomicInteger currentSize; private final DataSource dataSource; private final EvictionPolicy evictionPolicy; private final List> hooks; @@ -22,7 +23,7 @@ public FlipCache(int maxSize, DataSource dataSource, EvictionPolicy evi this.dataSource = dataSource; this.evictionPolicy = evictionPolicy; this.hooks = new ArrayList<>(); - this.currentSize = 0; + this.currentSize = new AtomicInteger(0); } private void triggerHooks(Event event, K key, V value) { @@ -48,7 +49,7 @@ public V getCache(K key) { } @Override - public V setCache(K key, V value) { + public synchronized V setCache(K key, V value) { // if key already exists if (dataSource.contains(key)) { evictionPolicy.keyAccessed(key); @@ -56,18 +57,18 @@ public V setCache(K key, V value) { } // If the cache is full, evict an item - if (currentSize == maxSize) { + if (currentSize.intValue() == maxSize) { K keyToEvict = evictionPolicy.evictionCandidate(); triggerHooks(Event.EVICT, keyToEvict, dataSource.retrieve(keyToEvict)); dataSource.remove(keyToEvict); evictionPolicy.keyRemoved(keyToEvict); - currentSize -= 1; + currentSize.getAndDecrement(); } // persist the data dataSource.persist(key, value); evictionPolicy.keyAdded(key); - currentSize += 1; + currentSize.getAndIncrement(); // Add appropriate metrics triggerHooks(Event.CREATE, key, value); From f5c7ed662a360adb20e4f68d2fa816913d510bab Mon Sep 17 00:00:00 2001 From: harshraj22 Date: Sat, 30 Aug 2025 23:48:55 +0530 Subject: [PATCH 4/6] Adds some documentation about implementation --- .../java/com/example/application/ports/outbound/CacheHook.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lld/flipCache/src/main/java/com/example/application/ports/outbound/CacheHook.java b/lld/flipCache/src/main/java/com/example/application/ports/outbound/CacheHook.java index 12b6904..5ccb25f 100644 --- a/lld/flipCache/src/main/java/com/example/application/ports/outbound/CacheHook.java +++ b/lld/flipCache/src/main/java/com/example/application/ports/outbound/CacheHook.java @@ -2,6 +2,9 @@ import com.example.application.models.Event; +// Note: Though this interface expects only one method, +// in general, it would need to maintain state (e.g., count of hits, misses, etc.) +// Hence, it is defined as an interface rather than a functional interface. public interface CacheHook { void onEvent(Event event, K key, V value); } From 159fd5a9242a34f7135115f54e6c3933bea9f505 Mon Sep 17 00:00:00 2001 From: harshraj22 Date: Sat, 30 Aug 2025 23:50:50 +0530 Subject: [PATCH 5/6] More multithreading update --- .../main/java/com/example/application/service/FlipCache.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lld/flipCache/src/main/java/com/example/application/service/FlipCache.java b/lld/flipCache/src/main/java/com/example/application/service/FlipCache.java index 80b6148..8486537 100644 --- a/lld/flipCache/src/main/java/com/example/application/service/FlipCache.java +++ b/lld/flipCache/src/main/java/com/example/application/service/FlipCache.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; public class FlipCache implements GetCache, SetCache { @@ -22,7 +23,7 @@ public FlipCache(int maxSize, DataSource dataSource, EvictionPolicy evi this.maxSize = maxSize; this.dataSource = dataSource; this.evictionPolicy = evictionPolicy; - this.hooks = new ArrayList<>(); + this.hooks = new CopyOnWriteArrayList<>(); this.currentSize = new AtomicInteger(0); } From ec98324fda9d556df53b45f7fbee85b9916e11f0 Mon Sep 17 00:00:00 2001 From: harshraj22 Date: Sat, 30 Aug 2025 23:55:16 +0530 Subject: [PATCH 6/6] Suggestions from Claude Sonnet for making the methods thread safe --- .../adaptor/outbound/InMemoryDataSource.java | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/lld/flipCache/src/main/java/com/example/adaptor/outbound/InMemoryDataSource.java b/lld/flipCache/src/main/java/com/example/adaptor/outbound/InMemoryDataSource.java index 80dcea4..01a8f51 100644 --- a/lld/flipCache/src/main/java/com/example/adaptor/outbound/InMemoryDataSource.java +++ b/lld/flipCache/src/main/java/com/example/adaptor/outbound/InMemoryDataSource.java @@ -13,8 +13,8 @@ public InMemoryDataSource() { } @Override - public synchronized V persist(K key, V value) { - data.put(key, value); + public V persist(K key, V value) { + return data.put(key, value); /* For Testing multi-threaded access and exception handling System.out.println("Persisted key: " + key + ", value: " + value + " in InMemoryDataSource. Size: " + data.size()); @@ -22,27 +22,16 @@ public synchronized V persist(K key, V value) { throw new RuntimeException("InMemoryDataSource capacity exceeded"); } */ - - return value; } @Override public V retrieve(K key) { - if (!this.contains(key)) { - return null; - } return data.get(key); } @Override public V remove(K key) { - if (!this.contains(key)) { - return null; - } - - V value = this.retrieve(key); - data.remove(key); - return value; + return data.remove(key); } @Override