diff --git a/offheap-resource/src/main/java/org/terracotta/offheapresource/OffHeapResource.java b/offheap-resource/src/main/java/org/terracotta/offheapresource/OffHeapResource.java new file mode 100644 index 0000000000..7e71c7538d --- /dev/null +++ b/offheap-resource/src/main/java/org/terracotta/offheapresource/OffHeapResource.java @@ -0,0 +1,58 @@ +/* + * The contents of this file are subject to the Terracotta Public 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://terracotta.org/legal/terracotta-public-license. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for + * the specific language governing rights and limitations under the License. + * + * The Covered Software is OffHeap Resource. + * + * The Initial Developer of the Covered Software is + * Terracotta, Inc., a Software AG company + */ + +package org.terracotta.offheapresource; + +import java.util.concurrent.atomic.AtomicLong; + +public class OffHeapResource { + + private final AtomicLong remaining; + + OffHeapResource(long size) { + if (size < 0) { + throw new IllegalArgumentException("Resource size cannot be negative"); + } else { + this.remaining = new AtomicLong(size); + } + } + + public boolean allocate(long size) { + if (size < 0) { + throw new IllegalArgumentException("Allocation size cannot be negative"); + } else { + for (long current = remaining.get(); current >= size; current = remaining.get()) { + if (remaining.compareAndSet(current, current - size)) { + return true; + } + } + return false; + } + } + + public void free(long size) { + if (size < 0) { + throw new IllegalArgumentException("Freed size cannot be negative"); + } else { + remaining.addAndGet(size); + } + } + + public long available() { + return remaining.get(); + } +} diff --git a/offheap-resource/src/main/java/org/terracotta/offheapresource/OffHeapResourceIdentifier.java b/offheap-resource/src/main/java/org/terracotta/offheapresource/OffHeapResourceIdentifier.java new file mode 100644 index 0000000000..64ca428e7e --- /dev/null +++ b/offheap-resource/src/main/java/org/terracotta/offheapresource/OffHeapResourceIdentifier.java @@ -0,0 +1,56 @@ +/* + * The contents of this file are subject to the Terracotta Public 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://terracotta.org/legal/terracotta-public-license. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for + * the specific language governing rights and limitations under the License. + * + * The Covered Software is OffHeap Resource. + * + * The Initial Developer of the Covered Software is + * Terracotta, Inc., a Software AG company + */ + +package org.terracotta.offheapresource; + +import org.terracotta.entity.ServiceConfiguration; + +/** + * + * @author cdennis + */ +public final class OffHeapResourceIdentifier implements ServiceConfiguration { + + private final String name; + + public static OffHeapResourceIdentifier identifier(String name) { + return new OffHeapResourceIdentifier(name); + } + + private OffHeapResourceIdentifier(String name) { + if (name == null) { + throw new NullPointerException("Name cannot be null"); + } else { + this.name = name; + } + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof OffHeapResourceIdentifier) && name.equals(((OffHeapResourceIdentifier) obj).name); + } + + @Override + public Class getServiceType() { + return OffHeapResource.class; + } +} diff --git a/offheap-resource/src/main/java/org/terracotta/offheapresource/OffHeapResourcesProvider.java b/offheap-resource/src/main/java/org/terracotta/offheapresource/OffHeapResourcesProvider.java index 19e05643c2..373deacd10 100644 --- a/offheap-resource/src/main/java/org/terracotta/offheapresource/OffHeapResourcesProvider.java +++ b/offheap-resource/src/main/java/org/terracotta/offheapresource/OffHeapResourcesProvider.java @@ -1,15 +1,34 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * The contents of this file are subject to the Terracotta Public 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://terracotta.org/legal/terracotta-public-license. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for + * the specific language governing rights and limitations under the License. + * + * The Covered Software is OffHeap Resource. + * + * The Initial Developer of the Covered Software is + * Terracotta, Inc., a Software AG company */ + package org.terracotta.offheapresource; import java.io.IOException; +import java.math.BigInteger; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + import org.terracotta.entity.ServiceConfiguration; import org.terracotta.entity.ServiceProvider; import org.terracotta.entity.ServiceProviderConfiguration; +import org.terracotta.offheapresource.config.MemoryUnit; +import org.terracotta.offheapresource.config.ResourceType; /** * @@ -17,23 +36,54 @@ */ class OffHeapResourcesProvider implements ServiceProvider { + private final Map resources = new HashMap(); + @Override - public boolean initialize(ServiceProviderConfiguration configuration) { - throw new UnsupportedOperationException(); + public synchronized boolean initialize(ServiceProviderConfiguration unknownConfig) { + if (unknownConfig instanceof OffHeapResourcesConfiguration) { + OffHeapResourcesConfiguration configuration = (OffHeapResourcesConfiguration) unknownConfig; + if (resources.isEmpty()) { + for (ResourceType r : configuration.getResources()) { + resources.put(OffHeapResourceIdentifier.identifier(r.getName()), new OffHeapResource(convert(r.getValue(), r.getUnit()).longValueExact())); + } + return true; + } else { + throw new IllegalStateException("Resources already initialized"); + } + } else { + return false; + } } @Override - public T getService(long consumerID, ServiceConfiguration configuration) { - throw new UnsupportedOperationException(); + public T getService(long consumerID, ServiceConfiguration unknownConfiguration) { + if (unknownConfiguration instanceof OffHeapResourceIdentifier) { + OffHeapResourceIdentifier identifier = (OffHeapResourceIdentifier) unknownConfiguration; + return (T) identifier.getServiceType().cast(resources.get(identifier)); + } else { + throw new IllegalArgumentException("Unexpected configuration type " + unknownConfiguration.getClass()); + } } @Override public Collection> getProvidedServiceTypes() { - throw new UnsupportedOperationException(); + return Collections.>singleton(OffHeapResource.class); } @Override - public void close() throws IOException { - throw new UnsupportedOperationException(); + public void close() { + resources.clear(); + } + + private static BigInteger convert(BigInteger value, MemoryUnit unit) { + switch (unit) { + case B: return value.shiftLeft(0); + case K_B: return value.shiftLeft(10); + case MB: return value.shiftLeft(20); + case GB: return value.shiftLeft(30); + case TB: return value.shiftLeft(40); + case PB: return value.shiftLeft(50); + } + throw new IllegalArgumentException("Unknown unit " + unit); } } diff --git a/offheap-resource/src/test/java/org/terracotta/offheapresource/OffHeapResourceIdentifierTest.java b/offheap-resource/src/test/java/org/terracotta/offheapresource/OffHeapResourceIdentifierTest.java new file mode 100644 index 0000000000..662b28b41f --- /dev/null +++ b/offheap-resource/src/test/java/org/terracotta/offheapresource/OffHeapResourceIdentifierTest.java @@ -0,0 +1,53 @@ +/* + * The contents of this file are subject to the Terracotta Public 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://terracotta.org/legal/terracotta-public-license. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for + * the specific language governing rights and limitations under the License. + * + * The Covered Software is OffHeap Resource. + * + * The Initial Developer of the Covered Software is + * Terracotta, Inc., a Software AG company + */ +package org.terracotta.offheapresource; + +import static org.hamcrest.core.Is.is; +import org.junit.Test; + +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +public class OffHeapResourceIdentifierTest { + + @Test + public void testNullName() { + try { + OffHeapResourceIdentifier.identifier(null); + fail("Expected NullPointerException"); + } catch (NullPointerException e) { + //expected + } + } + + @Test + public void testServiceType() { + assertThat(OffHeapResourceIdentifier.identifier("foo").getServiceType(), equalTo(OffHeapResource.class)); + } + + @Test + public void testEquals() { + assertThat(OffHeapResourceIdentifier.identifier("foo").equals(OffHeapResourceIdentifier.identifier("foo")), is(true)); + assertThat(OffHeapResourceIdentifier.identifier("foo").equals(OffHeapResourceIdentifier.identifier("bar")), is(false)); + } + + @Test + public void testHashcode() { + assertThat(OffHeapResourceIdentifier.identifier("foo").hashCode(), is(OffHeapResourceIdentifier.identifier("foo").hashCode())); + } +} diff --git a/offheap-resource/src/test/java/org/terracotta/offheapresource/OffHeapResourceTest.java b/offheap-resource/src/test/java/org/terracotta/offheapresource/OffHeapResourceTest.java new file mode 100644 index 0000000000..778c849a3c --- /dev/null +++ b/offheap-resource/src/test/java/org/terracotta/offheapresource/OffHeapResourceTest.java @@ -0,0 +1,91 @@ +/* + * The contents of this file are subject to the Terracotta Public 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://terracotta.org/legal/terracotta-public-license. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for + * the specific language governing rights and limitations under the License. + * + * The Covered Software is OffHeap Resource. + * + * The Initial Developer of the Covered Software is + * Terracotta, Inc., a Software AG company + */ + +package org.terracotta.offheapresource; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import org.junit.Test; + +public class OffHeapResourceTest { + + @Test + public void testNegativeResourceSize() { + try { + new OffHeapResource(-1); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + //expected; + } + } + + @Test + public void testZeroSizeResourceIsUseless() { + OffHeapResource ohr = new OffHeapResource(0); + assertThat(ohr.allocate(1), is(false)); + assertThat(ohr.available(), is(0L)); + } + + @Test + public void testAllocationReducesSize() { + OffHeapResource ohr = new OffHeapResource(20); + assertThat(ohr.available(), is(20L)); + assertThat(ohr.allocate(10), is(true)); + assertThat(ohr.available(), is(10L)); + } + + @Test + public void testNegativeAllocationFails() { + OffHeapResource ohr = new OffHeapResource(20); + try { + ohr.allocate(-1); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + //expected + } + } + + @Test + public void testAllocationWhenExhaustedFails() { + OffHeapResource ohr = new OffHeapResource(20); + ohr.allocate(20); + assertThat(ohr.allocate(1), is(false)); + assertThat(ohr.available(), is(0L)); + } + + @Test + public void testFreeIncreasesSize() { + OffHeapResource ohr = new OffHeapResource(20); + ohr.allocate(20); + assertThat(ohr.available(), is(0L)); + ohr.free(10); + assertThat(ohr.available(), is(10L)); + } + + @Test + public void testNegativeFreeFails() { + OffHeapResource ohr = new OffHeapResource(20); + ohr.allocate(10); + try { + ohr.free(-10); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + //expected + } + } +} diff --git a/offheap-resource/src/test/java/org/terracotta/offheapresource/OffHeapResourcesProviderTest.java b/offheap-resource/src/test/java/org/terracotta/offheapresource/OffHeapResourcesProviderTest.java new file mode 100644 index 0000000000..400c828409 --- /dev/null +++ b/offheap-resource/src/test/java/org/terracotta/offheapresource/OffHeapResourcesProviderTest.java @@ -0,0 +1,119 @@ +/* + * The contents of this file are subject to the Terracotta Public 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://terracotta.org/legal/terracotta-public-license. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for + * the specific language governing rights and limitations under the License. + * + * The Covered Software is OffHeap Resource. + * + * The Initial Developer of the Covered Software is + * Terracotta, Inc., a Software AG company + */ + +package org.terracotta.offheapresource; + +import java.math.BigInteger; +import java.util.Collections; +import org.junit.Test; +import org.terracotta.entity.ServiceProvider; +import org.terracotta.entity.ServiceProviderConfiguration; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNull.notNullValue; +import static org.hamcrest.core.IsNull.nullValue; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import org.terracotta.offheapresource.config.MemoryUnit; +import org.terracotta.offheapresource.config.ResourceType; + +public class OffHeapResourcesProviderTest { + + @Test + public void testInitializeWithWrongConfig() { + OffHeapResourcesProvider provider = new OffHeapResourcesProvider(); + + assertThat(provider.initialize(new ServiceProviderConfiguration() { + @Override + public Class getServiceProviderType() { + return OffHeapResourcesProvider.class; + } + }), is(false)); + } + + @Test + public void testInitializeWithValidConfig() { + ResourceType resourceConfig = mock(ResourceType.class); + when(resourceConfig.getName()).thenReturn("foo"); + when(resourceConfig.getUnit()).thenReturn(MemoryUnit.MB); + when(resourceConfig.getValue()).thenReturn(BigInteger.valueOf(2)); + + OffHeapResourcesConfiguration config = mock(OffHeapResourcesConfiguration.class); + when(config.getResources()).thenReturn(Collections.singleton(resourceConfig)); + + OffHeapResourcesProvider provider = new OffHeapResourcesProvider(); + provider.initialize(config); + + assertThat(provider.getService(42L, OffHeapResourceIdentifier.identifier("foo")), notNullValue()); + assertThat(provider.getService(42L, OffHeapResourceIdentifier.identifier("foo")).available(), is(2L * 1024 * 1024)); + } + + @Test + public void testDoubleInitialize() { + ResourceType resourceConfig = mock(ResourceType.class); + when(resourceConfig.getName()).thenReturn("foo"); + when(resourceConfig.getUnit()).thenReturn(MemoryUnit.MB); + when(resourceConfig.getValue()).thenReturn(BigInteger.valueOf(2)); + + OffHeapResourcesConfiguration config = mock(OffHeapResourcesConfiguration.class); + when(config.getResources()).thenReturn(Collections.singleton(resourceConfig)); + + OffHeapResourcesProvider provider = new OffHeapResourcesProvider(); + provider.initialize(config); + try { + provider.initialize(config); + fail("Expected IllegalStateException"); + } catch (IllegalStateException e) { + //expected + } + } + + @Test + public void testNullReturnOnInvalidResource() { + ResourceType resourceConfig = mock(ResourceType.class); + when(resourceConfig.getName()).thenReturn("foo"); + when(resourceConfig.getUnit()).thenReturn(MemoryUnit.MB); + when(resourceConfig.getValue()).thenReturn(BigInteger.valueOf(2)); + + OffHeapResourcesConfiguration config = mock(OffHeapResourcesConfiguration.class); + when(config.getResources()).thenReturn(Collections.singleton(resourceConfig)); + + OffHeapResourcesProvider provider = new OffHeapResourcesProvider(); + provider.initialize(config); + + assertThat(provider.getService(42L, OffHeapResourceIdentifier.identifier("bar")), nullValue()); + } + + + @Test + public void testNullReturnAfterClose() { + ResourceType resourceConfig = mock(ResourceType.class); + when(resourceConfig.getName()).thenReturn("foo"); + when(resourceConfig.getUnit()).thenReturn(MemoryUnit.MB); + when(resourceConfig.getValue()).thenReturn(BigInteger.valueOf(2)); + + OffHeapResourcesConfiguration config = mock(OffHeapResourcesConfiguration.class); + when(config.getResources()).thenReturn(Collections.singleton(resourceConfig)); + + OffHeapResourcesProvider provider = new OffHeapResourcesProvider(); + provider.initialize(config); + provider.close(); + assertThat(provider.getService(42L, OffHeapResourceIdentifier.identifier("foo")), nullValue()); + } +}