/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package com.github.geophile.erdo.immutableitemcache; import org.junit.Ignore; import org.junit.Test; import java.io.IOException; import java.util.*; import static org.junit.Assert.*; public class ImmutableItemCacheMultithreadedTest { // Ignored because observer doesn't seem to work right in a multithreaded test, which this is. @Ignore @Test public void test() throws InterruptedException, IOException { assertTrue(CACHE_SIZE >= 2 * THREADS); CacheObserver cacheObserver = new CacheObserver(); cache = new ImmutableItemCache<>(CACHE_SIZE, cacheObserver); // Load cache and mark one item evictable for each thread int id = 0; while (id < CACHE_SIZE) { Item item = cache.find(id(id), ITEM_READER); item.recentAccess(false); if (id < THREADS) { item.okToEvict(true); cacheObserver.expectedVictim = item; } else { item.okToEvict(false); } id++; } System.out.println("Cache loaded"); TestThread[] threads = new TestThread[THREADS]; for (int t = 0; t < THREADS; t++) { threads[t] = new TestThread(t); } for (TestThread thread : threads) { thread.start(); } List<Throwable> crashes = new ArrayList<>(); for (TestThread thread : threads) { thread.join(); if (thread.termination != null) { crashes.add(thread.termination); } } for (Throwable crash : crashes) { crash.printStackTrace(); } assertTrue(crashes.isEmpty()); List<Integer> expectedSorted = new ArrayList<>(); for (Item item : cacheObserver.expected) { expectedSorted.add(item.id().value()); } Collections.sort(expectedSorted); List<Integer> actualSorted = new ArrayList<>(); for (Id actualId : cache.cacheContents().keySet()) { actualSorted.add(actualId.value()); } Collections.sort(actualSorted); assertEquals(expectedSorted, actualSorted); } private Id id(int id) { return new Id(id); } private static final int CACHE_SIZE = 20; private static final int MAX_ID = CACHE_SIZE * 2; private static final int OPS = 10000; private static final int THREADS = 10; private static final ImmutableItemManager<Id, Item> ITEM_READER = new ImmutableItemManager<Id, Item>() { public Item getItemForCache(Id id) { return new Item(id); } @Override public void cleanupItemEvictedFromCache(Item item) throws IOException, InterruptedException { } }; private ImmutableItemCache<Id, Item> cache; private static class CacheObserver implements CacheEntryList.Observer<Id, Item> { public void adding(Item item) { if (expected.isEmpty()) { item.okToEvict(true); expectedVictim = item; } expected.add(item); /* System.out.println(String.format("%s: Cache after adding %s: %s", Thread.currentThread() .getName(), item, describeExpected())); */ } public void evicting(Item victim) { assertNotNull(victim); assertNotNull(expectedVictim); assertEquals(expectedVictim.id().value(), victim.id().value()); boolean removed = expected.remove(victim); assertTrue(removed); if (expected.isEmpty()) { expectedVictim = null; } else { // Pick another entry to evict int n = random.nextInt(CACHE_SIZE) + 1; expectedVictim = victim; for (int i = 0; i < n; i++) { expectedVictim = expectedVictim.next(); } if (victim.id().equals(expectedVictim.id())) { expectedVictim = expectedVictim.next(); } expectedVictim.okToEvict(true); } /* System.out.println(String.format("%s: Cache after evicting %s: %s, next victim: %s", Thread.currentThread().getName(), victim, describeExpected(), expectedVictim)); */ } private String describeExpected() { StringBuilder buffer = new StringBuilder(); buffer.append('['); List<Item> sorted = new ArrayList<>(expected); Collections.sort(sorted); boolean first = true; for (Item item : sorted) { if (first) { first = false; } else { buffer.append(", "); } buffer.append(item.id().value()); if (item.okToEvict()) { buffer.append('*'); } } buffer.append(']'); return buffer.toString(); } private final Random random = new Random(419); public volatile Item expectedVictim; public Set<Item> expected = new HashSet<>(); } private class TestThread extends Thread { public void run() { try { // Operate on cache, tracking expected contents for (int i = 0; i < OPS; i++) { // Pick random id to read Id id = id(random.nextInt(MAX_ID)); /* System.out.println(String.format("%s: OP %s: find %s", getName(), i, id)); */ // Get new cache item and check eviction cache.find(id, ITEM_READER); } } catch (Throwable e) { e.printStackTrace(); termination = e; } } public TestThread(int threadId) { setName("t" + threadId); this.random = new Random(123456 + threadId); } private final Random random; public Throwable termination; } }