/* * Copyright 2015 Terracotta, Inc., a Software AG company. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.terracotta.offheapstore.eviction; import org.terracotta.offheapstore.eviction.EvictionListener; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.Callable; import org.junit.Assert; import org.junit.Test; import org.terracotta.offheapstore.buffersource.OffHeapBufferSource; import org.terracotta.offheapstore.concurrent.ConcurrentOffHeapClockCache; import org.terracotta.offheapstore.concurrent.ConcurrentOffHeapHashMap; import org.terracotta.offheapstore.concurrent.ConcurrentWriteLockedOffHeapClockCache; import org.terracotta.offheapstore.exceptions.OversizeMappingException; import org.terracotta.offheapstore.paging.PageSource; import org.terracotta.offheapstore.paging.UpfrontAllocatingPageSource; import org.terracotta.offheapstore.storage.LongStorageEngine; import org.terracotta.offheapstore.storage.OffHeapBufferHalfStorageEngine; import org.terracotta.offheapstore.storage.portability.StringPortability; /** * * @author Chris Dennis */ public class EvictionListenerIT { @Test public void testEvictionListenerReadWriteLocked() { MonitoringEvictionListener listener = new MonitoringEvictionListener(); PageSource source = new UpfrontAllocatingPageSource(new OffHeapBufferSource(), 16 * 4096, 2048); Map<Long, String> map = new ConcurrentOffHeapClockCache<Long, String>(source, LongStorageEngine.createFactory(OffHeapBufferHalfStorageEngine.createFactory(source, 128, StringPortability.INSTANCE)), listener); for (long i = 0; i < 2000L; i++) { map.put(Long.valueOf(i), Long.toString(i)); } Set<Long> evictedKeys = listener.evictedKeys(); Set<Long> presentKeys = map.keySet(); for (long i = 0; i < 2000L; i++) { Assert.assertEquals("Key: " + i + " map:" + presentKeys.contains(i) + " evicted:" + evictedKeys.contains(i), presentKeys.contains(i), !evictedKeys.contains(i)); } } @Test public void testEvictionListenerWriteLocked() { MonitoringEvictionListener listener = new MonitoringEvictionListener(); PageSource source = new UpfrontAllocatingPageSource(new OffHeapBufferSource(), 16 * 4096, 2048); Map<Long, String> map = new ConcurrentWriteLockedOffHeapClockCache<Long, String>(source, LongStorageEngine.createFactory(OffHeapBufferHalfStorageEngine.createFactory(source, 128, StringPortability.INSTANCE)), listener); for (long i = 0; i < 2000L; i++) { map.put(Long.valueOf(i), Long.toString(i)); } Set<Long> evictedKeys = listener.evictedKeys(); Set<Long> presentKeys = map.keySet(); for (long i = 0; i < 2000L; i++) { Assert.assertEquals("Key: " + i + " map:" + presentKeys.contains(i) + " evicted:" + evictedKeys.contains(i), presentKeys.contains(i), !evictedKeys.contains(i)); } } @Test public void testEvictionListenerThatThrowsReadWriteLocked() { ThrowingEvictionListener listener = new ThrowingEvictionListener(); PageSource source = new UpfrontAllocatingPageSource(new OffHeapBufferSource(), 16 * 4096, 2048); Map<Long, String> map = new ConcurrentOffHeapClockCache<Long, String>(source, LongStorageEngine.createFactory(OffHeapBufferHalfStorageEngine.createFactory(source, 128, StringPortability.INSTANCE)), listener); for (long i = 0; i < 2000L; i++) { try { map.put(Long.valueOf(i), Long.toString(i)); } catch (NullPointerException e) { //ignore } } Set<Long> evictedKeys = listener.evictedKeys(); for (Long l : evictedKeys) { Assert.assertFalse(map.containsKey(l)); } } @Test public void testEvictionListenerThatThrowsWriteLocked() { ThrowingEvictionListener listener = new ThrowingEvictionListener(); PageSource source = new UpfrontAllocatingPageSource(new OffHeapBufferSource(), 16 * 4096, 2048); Map<Long, String> map = new ConcurrentWriteLockedOffHeapClockCache<Long, String>(source, LongStorageEngine.createFactory(OffHeapBufferHalfStorageEngine.createFactory(source, 128, StringPortability.INSTANCE)), listener); for (long i = 0; i < 2000L; i++) { try { map.put(Long.valueOf(i), Long.toString(i)); } catch (NullPointerException e) { //ignore } } Set<Long> evictedKeys = listener.evictedKeys(); for (Long l : evictedKeys) { Assert.assertFalse(map.containsKey(l)); } } @Test public void testEvictionListenerSeesStealingEventsReadWriteLocked() { MonitoringEvictionListener listener = new MonitoringEvictionListener(); PageSource source = new UpfrontAllocatingPageSource(new OffHeapBufferSource(), 16 * 4096, 16 * 4096); Map<Long, String> victim = new ConcurrentOffHeapClockCache<Long, String>(source, LongStorageEngine.createFactory(OffHeapBufferHalfStorageEngine.createFactory(source, 128, StringPortability.INSTANCE, false, true)), listener); long i = 0; while (listener.evictedKeys().isEmpty()) { victim.put(Long.valueOf(i), Long.toString(i)); i++; } listener.evictedKeys().clear(); long victimSize = victim.size(); Map<Long, String> thief = new ConcurrentOffHeapHashMap<Long, String>(source, true, LongStorageEngine.createFactory(OffHeapBufferHalfStorageEngine.createFactory(source, 128, StringPortability.INSTANCE, true, false))); try { i = 0; while (true) { thief.put(Long.valueOf(i), Long.toString(i)); i++; } } catch (OversizeMappingException e) { //ignore } Assert.assertFalse(listener.evictedKeys().isEmpty()); Assert.assertEquals(victimSize, victim.size() + listener.evictedKeys().size()); } @Test public void testEvictionListenerSeesStealingEventsWriteLocked() { MonitoringEvictionListener listener = new MonitoringEvictionListener(); PageSource source = new UpfrontAllocatingPageSource(new OffHeapBufferSource(), 16 * 4096, 16 * 4096); Map<Long, String> victim = new ConcurrentWriteLockedOffHeapClockCache<Long, String>(source, LongStorageEngine.createFactory(OffHeapBufferHalfStorageEngine.createFactory(source, 128, StringPortability.INSTANCE, false, true)), listener); long i = 0; while (listener.evictedKeys().isEmpty()) { victim.put(Long.valueOf(i), Long.toString(i)); i++; } listener.evictedKeys().clear(); long victimSize = victim.size(); Map<Long, String> thief = new ConcurrentOffHeapHashMap<Long, String>(source, true, LongStorageEngine.createFactory(OffHeapBufferHalfStorageEngine.createFactory(source, 128, StringPortability.INSTANCE, true, false))); try { i = 0; while (true) { thief.put(Long.valueOf(i++), Long.toString(i)); } } catch (OversizeMappingException e) { //ignore } Assert.assertFalse(listener.evictedKeys().isEmpty()); Assert.assertEquals(victimSize, victim.size() + listener.evictedKeys().size()); } static class MonitoringEvictionListener implements EvictionListener<Long, String> { private final Set<Long> evictedKeys = new HashSet<Long>(); @Override public void evicting(Callable<Entry<Long, String>> evictee) { try { Entry<Long, String> evicted = evictee.call(); Assert.assertEquals(evicted.getKey().longValue(), Long.parseLong(evicted.getValue())); Assert.assertTrue(evictedKeys.add(evicted.getKey())); } catch (Exception e) { throw new RuntimeException(e); } } public Set<Long> evictedKeys() { return evictedKeys; } } static class ThrowingEvictionListener implements EvictionListener<Long, String> { private final Set<Long> evictedKeys = new HashSet<Long>(); @Override public void evicting(Callable<Entry<Long, String>> evictee) { try { Entry<Long, String> evicted = evictee.call(); Assert.assertEquals(evicted.getKey().longValue(), Long.parseLong(evicted.getValue())); Assert.assertTrue(evictedKeys.add(evicted.getKey())); } catch (Exception e) { throw new RuntimeException(e); } throw new NullPointerException(); } public Set<Long> evictedKeys() { return evictedKeys; } } }