From: Robert Varga Date: Wed, 26 Jan 2022 10:04:39 +0000 (+0100) Subject: Add SoftSchemaSourceCache X-Git-Tag: v8.0.0~57 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=yangtools.git;a=commitdiff_plain;h=e42ffb79883e16852f14a22d6225a7c1211944a8 Add SoftSchemaSourceCache GuavaSchemaSourceCache has a rather ugly design, which cannot be easily scaled. Introduce a simpler SoftSchemaSourceCache as its replacement. JIRA: YANGTOOLS-1391 Change-Id: I0de9092885ede3efff9d76d48ec14b85a69818b2 Signed-off-by: Robert Varga (cherry picked from commit 0cd2c12e31e8bdf3463b8282a6c6cc2e6995c140) --- diff --git a/yang/yang-repo-spi/src/main/java/org/opendaylight/yangtools/yang/model/repo/spi/GuavaSchemaSourceCache.java b/yang/yang-repo-spi/src/main/java/org/opendaylight/yangtools/yang/model/repo/spi/GuavaSchemaSourceCache.java index ce337c8297..4720e37519 100644 --- a/yang/yang-repo-spi/src/main/java/org/opendaylight/yangtools/yang/model/repo/spi/GuavaSchemaSourceCache.java +++ b/yang/yang-repo-spi/src/main/java/org/opendaylight/yangtools/yang/model/repo/spi/GuavaSchemaSourceCache.java @@ -28,8 +28,10 @@ import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier; * A simple {@link AbstractSchemaSourceCache} based on {@link Cache Guava Cache}. * * @param {@link SchemaSourceRepresentation} type stored in this cache + * @deprecated This class has a rather complicated and ugly design. Use {@link SoftSchemaSourceCache} instead. */ @Beta +@Deprecated(since = "7.0.13", forRemoval = true) public final class GuavaSchemaSourceCache extends AbstractSchemaSourceCache implements AutoCloseable { // FIXME: 7.0.0: use a java.util.Cleaner? diff --git a/yang/yang-repo-spi/src/main/java/org/opendaylight/yangtools/yang/model/repo/spi/SoftSchemaSourceCache.java b/yang/yang-repo-spi/src/main/java/org/opendaylight/yangtools/yang/model/repo/spi/SoftSchemaSourceCache.java new file mode 100644 index 0000000000..cdbc4b6889 --- /dev/null +++ b/yang/yang-repo-spi/src/main/java/org/opendaylight/yangtools/yang/model/repo/spi/SoftSchemaSourceCache.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2022 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.model.repo.spi; + +import com.google.common.annotations.Beta; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import java.lang.ref.Cleaner; +import java.lang.ref.Cleaner.Cleanable; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import org.opendaylight.yangtools.concepts.Registration; +import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException; +import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation; +import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier; +import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource.Costs; + +/** + * A simple {@link AbstractSchemaSourceCache} maintaining soft references. + * + * @param {@link SchemaSourceRepresentation} type stored in this cache + */ +@Beta +public final class SoftSchemaSourceCache extends AbstractSchemaSourceCache + implements AutoCloseable { + private static final Cleaner CLEANER = Cleaner.create(); + + private final ConcurrentMap> references = new ConcurrentHashMap<>(); + private final ConcurrentMap cleanables = new ConcurrentHashMap<>(); + + private boolean closed; + + public SoftSchemaSourceCache(final SchemaSourceRegistry consumer, final Class representation) { + super(consumer, representation, Costs.IMMEDIATE); + } + + @Override + public ListenableFuture getSource(final SourceIdentifier sourceIdentifier) { + final var ref = references.get(sourceIdentifier); + if (ref != null) { + final var src = ref.get(); + if (src != null) { + // We have a hit + return Futures.immediateFuture(src); + } + + // Expired entry: remove it + references.remove(sourceIdentifier, ref); + } + + return Futures.immediateFailedFuture(new MissingSchemaSourceException("Source not found", sourceIdentifier)); + } + + @Override + public synchronized void close() { + if (!closed) { + closed = true; + while (!cleanables.isEmpty()) { + cleanables.values().forEach(Cleanable::clean); + } + } + } + + @Override + protected synchronized void offer(final T source) { + if (closed) { + return; + } + + final var id = source.getIdentifier(); + final var ref = new SoftReference<>(source); + + while (true) { + final var prev = references.putIfAbsent(id, ref); + if (prev == null) { + // We have performed a fresh insert and need to add a cleanup + break; + } + + if (prev.get() != null) { + // We still have a source for this identifier, no further action is needed + return; + } + + // Existing reference is dead, remove it and retry + references.remove(id, prev); + } + + // We have populated a cache entry, register the source and a cleanup action + final var reg = register(id); + cleanables.put(reg, CLEANER.register(source, () -> { + cleanables.remove(reg); + reg.close(); + references.remove(id, ref); + })); + + // Ensure 'source' is still reachable here. This is needed to ensure the cleanable action does not fire before + // we have had a chance to insert it into the map. + Reference.reachabilityFence(source); + } +} diff --git a/yang/yang-repo-spi/src/test/java/org/opendaylight/yangtools/yang/model/repo/spi/GuavaSchemaSourceCacheTest.java b/yang/yang-repo-spi/src/test/java/org/opendaylight/yangtools/yang/model/repo/spi/GuavaSchemaSourceCacheTest.java index b8e3494773..3e2c637c2b 100644 --- a/yang/yang-repo-spi/src/test/java/org/opendaylight/yangtools/yang/model/repo/spi/GuavaSchemaSourceCacheTest.java +++ b/yang/yang-repo-spi/src/test/java/org/opendaylight/yangtools/yang/model/repo/spi/GuavaSchemaSourceCacheTest.java @@ -31,6 +31,7 @@ import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier; import org.opendaylight.yangtools.yang.model.repo.api.YangSchemaSourceRepresentation; import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource; +@Deprecated @RunWith(MockitoJUnitRunner.StrictStubs.class) public class GuavaSchemaSourceCacheTest { public static final Class REPRESENTATION = YangSchemaSourceRepresentation.class; diff --git a/yang/yang-repo-spi/src/test/java/org/opendaylight/yangtools/yang/model/repo/spi/SoftSchemaSourceCacheTest.java b/yang/yang-repo-spi/src/test/java/org/opendaylight/yangtools/yang/model/repo/spi/SoftSchemaSourceCacheTest.java new file mode 100644 index 0000000000..e699da45ef --- /dev/null +++ b/yang/yang-repo-spi/src/test/java/org/opendaylight/yangtools/yang/model/repo/spi/SoftSchemaSourceCacheTest.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.model.repo.spi; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; + +import com.google.common.base.MoreObjects.ToStringHelper; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.opendaylight.yangtools.yang.common.Revision; +import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier; +import org.opendaylight.yangtools.yang.model.repo.api.YangSchemaSourceRepresentation; +import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource; + +@RunWith(MockitoJUnitRunner.StrictStubs.class) +public class SoftSchemaSourceCacheTest { + public static final Class REPRESENTATION = YangSchemaSourceRepresentation.class; + public static final long LIFETIME = 1000L; + public static final TimeUnit UNITS = TimeUnit.MILLISECONDS; + + @Mock + public SchemaSourceRegistry registry; + @Mock + public SchemaSourceRegistration registration; + + @Before + public void setUp() { + doNothing().when(registration).close(); + doReturn(registration).when(registry).registerSchemaSource(any(SchemaSourceProvider.class), + any(PotentialSchemaSource.class)); + } + + @Test + public void inMemorySchemaSourceCacheTest() { + try (var cache = new SoftSchemaSourceCache<>(registry, REPRESENTATION)) { + assertNotNull(cache); + } + } + + @Test + public void inMemorySchemaSourceCacheOfferAndGetSourcestest() throws Exception { + try (var cache = new SoftSchemaSourceCache<>(registry, REPRESENTATION)) { + final String content = "content"; + final YangTextSchemaSource source = new TestingYangSource("test", "2012-12-12", content); + cache.offer(source); + final var sourceIdentifier = RevisionSourceIdentifier.create("test", Revision.of("2012-12-12")); + final var checkedSource = cache .getSource(sourceIdentifier); + assertNotNull(checkedSource); + final var yangSchemaSourceRepresentation = checkedSource.get(); + assertNotNull(yangSchemaSourceRepresentation); + assertEquals(sourceIdentifier, yangSchemaSourceRepresentation.getIdentifier()); + } + } + + @Test + public void inMemorySchemaSourceCacheNullGetSourcestest() throws Exception { + try (var cache = new SoftSchemaSourceCache<>(registry, REPRESENTATION)) { + final var sourceIdentifier = RevisionSourceIdentifier.create("test", Revision.of("2012-12-12")); + final var checkedSource = cache.getSource(sourceIdentifier); + assertNotNull(checkedSource); + assertThrows(ExecutionException.class, () -> checkedSource.get()); + } + } + + @Test + public void inMemorySchemaSourceCache3test() throws InterruptedException, ExecutionException { + try (var cache1 = new SoftSchemaSourceCache<>(registry, REPRESENTATION)) { + try (var cache2 = new SoftSchemaSourceCache<>(registry, REPRESENTATION)) { + final String content = "content"; + final YangTextSchemaSource source = new TestingYangSource("test", "2012-12-12", content); + cache1.offer(source); + cache2.offer(source); + + final var sourceIdentifier = RevisionSourceIdentifier.create("test", Revision.of("2012-12-12")); + final var checkedSource = cache1.getSource(sourceIdentifier); + final var checkedSource2 = cache2.getSource(sourceIdentifier); + assertNotNull(checkedSource); + assertNotNull(checkedSource2); + + assertEquals(checkedSource.get(), checkedSource2.get()); + } + } + } + + private static class TestingYangSource extends YangTextSchemaSource { + private final String content; + + TestingYangSource(final String name, final String revision, final String content) { + super(RevisionSourceIdentifier.create(name, Revision.ofNullable(revision))); + this.content = content; + } + + @Override + public InputStream openStream() { + return new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public Optional getSymbolicName() { + return Optional.empty(); + } + + @Override + protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) { + return toStringHelper; + } + } +}