/* * 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; import org.terracotta.offheapstore.ReadWriteLockedOffHeapHashMap; import org.terracotta.offheapstore.OffHeapHashMap; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; import org.junit.Test; import org.terracotta.offheapstore.buffersource.HeapBufferSource; import org.terracotta.offheapstore.paging.PageSource; import org.terracotta.offheapstore.paging.UnlimitedPageSource; import org.terracotta.offheapstore.storage.HalfStorageEngine; import org.terracotta.offheapstore.storage.OffHeapBufferStorageEngine; import org.terracotta.offheapstore.storage.PointerSize; import org.terracotta.offheapstore.storage.SplitStorageEngine; import org.terracotta.offheapstore.storage.StorageEngine; import org.terracotta.offheapstore.storage.StorageEngine.Owner; import org.terracotta.offheapstore.storage.portability.SerializablePortability; public class IteratorMutationIT { @Test public void testConcurrentUpdateDoesntMiss() { PageSource source = new UnlimitedPageSource(new HeapBufferSource()); ReadWriteLockedOffHeapHashMap<Value, Value> map = new ReadWriteLockedOffHeapHashMap<Value, Value>(source, ValueStorage.INSTANCE, 8); map.put(new Value(0), new Value(0)); map.put(new Value(1), new Value(1)); map.put(new Value(2), new Value(2)); assertThat(map.getAtTableOffset(2 * OffHeapHashMap.ENTRY_SIZE), is(new Value(0))); assertThat(map.getAtTableOffset(3 * OffHeapHashMap.ENTRY_SIZE), is(new Value(1))); assertThat(map.getAtTableOffset(4 * OffHeapHashMap.ENTRY_SIZE), is(new Value(2))); Iterator<Value> keyIterator = map.keySet().iterator(); assertTrue(keyIterator.hasNext()); assertThat(keyIterator.next(), is(new Value(0))); map.remove(new Value(0)); map.put(new Value(2), new Value(2)); assertTrue(keyIterator.hasNext()); assertThat(keyIterator.next(), is(new Value(1))); assertTrue(keyIterator.hasNext()); assertThat(keyIterator.next(), is(new Value(2))); } @Test public void testConcurrentUpdateDoesntDuplicate() { PageSource source = new UnlimitedPageSource(new HeapBufferSource()); ReadWriteLockedOffHeapHashMap<Value, Value> map = new ReadWriteLockedOffHeapHashMap<Value, Value>(source, ValueStorage.INSTANCE, 4); map.put(new Value(0), new Value(0)); map.put(new Value(1), new Value(1)); map.put(new Value(2), new Value(2)); assertThat(map.getAtTableOffset(0 * OffHeapHashMap.ENTRY_SIZE), is(new Value(2))); assertThat(map.getAtTableOffset(2 * OffHeapHashMap.ENTRY_SIZE), is(new Value(0))); assertThat(map.getAtTableOffset(3 * OffHeapHashMap.ENTRY_SIZE), is(new Value(1))); Iterator<Value> keyIterator = map.keySet().iterator(); assertTrue(keyIterator.hasNext()); assertThat(keyIterator.next(), is(new Value(2))); map.remove(new Value(1)); map.put(new Value(2), new Value(2)); assertTrue(keyIterator.hasNext()); assertThat(keyIterator.next(), is(new Value(0))); try { assertFalse(keyIterator.hasNext()); } catch (AssertionError e) { throw new AssertionError("Expected no next value, seeing : " + keyIterator.next()); } } @Test public void testConcurrentResizeAndUpdate() { PageSource source = new UnlimitedPageSource(new HeapBufferSource()); ReadWriteLockedOffHeapHashMap<Value, Value> map = new ReadWriteLockedOffHeapHashMap<Value, Value>(source, new OffHeapBufferStorageEngine<Value, Value>(PointerSize.INT, source, 1024, new SerializablePortability(), new SerializablePortability()), 2); map.put(new Value(0), new Value(0)); map.put(new Value(1), new Value(1)); assertThat(map.getAtTableOffset(0 * OffHeapHashMap.ENTRY_SIZE), is(new Value(0))); assertThat(map.getAtTableOffset(1 * OffHeapHashMap.ENTRY_SIZE), is(new Value(1))); Iterator<Value> keyIterator = map.keySet().iterator(); map.put(new Value(2), new Value(2)); map.put(new Value(0), new Value(0)); assertThat(map.getAtTableOffset(0 * OffHeapHashMap.ENTRY_SIZE), is(new Value(2))); assertThat(map.getAtTableOffset(2 * OffHeapHashMap.ENTRY_SIZE), is(new Value(0))); assertThat(map.getAtTableOffset(3 * OffHeapHashMap.ENTRY_SIZE), is(new Value(1))); Collection<Value> iteratorKeys = new ArrayList<Value>(); while (keyIterator.hasNext()) { iteratorKeys.add(keyIterator.next()); } assertThat(iteratorKeys, new TypeSafeMatcher<Collection<?>>() { @Override public void describeTo(Description description) { description.appendText("a collection with no duplicates"); } @Override public boolean matchesSafely(Collection<?> objects) { return new HashSet<Object>(objects).size() == objects.size(); }}); } static class Value implements Serializable { private static final long serialVersionUID = 1L; private final int value; public Value(int value) { this.value = value; } @Override public boolean equals(Object o) { if (o instanceof Value) { return value == ((Value) o).value; } else { return false; } } @Override public int hashCode() { return 0; } @Override public String toString() { return "Value(" + value + ")"; } } static class ValueStorage implements HalfStorageEngine<Value> { private static final ValueStorage HALF_INSTANCE = new ValueStorage(); public static final StorageEngine<Value, Value> INSTANCE = new SplitStorageEngine<IteratorMutationIT.Value, IteratorMutationIT.Value>(HALF_INSTANCE, HALF_INSTANCE); @Override public Integer write(Value object, int hash) { return object.value; } @Override public void free(int encoding) { //no-op } @Override public Value read(int encoding) { return new Value(encoding); } @Override public boolean equals(Object object, int encoding) { if (object instanceof Value) { return ((Value) object).value == encoding; } else { return false; } } @Override public void clear() { //no-op } @Override public long getAllocatedMemory() { return 0L; } @Override public long getOccupiedMemory() { return 0L; } @Override public long getVitalMemory() { return 0L; } @Override public long getDataSize() { return 0L; } @Override public void invalidateCache() { //no-op } @Override public void bind(Owner owner, long mask) { //no-op } @Override public void destroy() { //no-op } @Override public boolean shrink() { return false; } } }