Test also WatchServiceCacheFactory

This commit is contained in:
Peter Palaga
2021-01-24 20:24:07 +01:00
parent 4d89464f8a
commit d3ea30f5d3
5 changed files with 70 additions and 49 deletions

View File

@@ -31,6 +31,9 @@ public class DefaultCacheFactory implements CacheFactory {
private final CacheFactory delegate;
public DefaultCacheFactory() {
/* java.nio.file.attribute.BasicFileAttributes.fileKey() is always null on Windows
* and we do not hold relying solely on java.nio.file.attribute.BasicFileAttributes#lastModifiedTime()
* for sufficient. So we rather rely on WatchService on Windows */
this.delegate = Os.current() == Os.WINDOWS ? new WatchServiceCacheFactory() : new TimestampCacheFactory();
}

View File

@@ -28,7 +28,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.codehaus.plexus.logging.AbstractLogEnabled;
import org.mvndaemon.mvnd.cache.Cache;
import org.mvndaemon.mvnd.cache.CacheFactory;
import org.mvndaemon.mvnd.cache.CacheRecord;
@@ -37,7 +36,7 @@ import org.mvndaemon.mvnd.cache.CacheRecord;
* A factory for {@link Cache} objects invalidating its entries based on {@link BasicFileAttributes#lastModifiedTime()}
* and {@link java.nio.file.attribute.BasicFileAttributes#fileKey()}.
*/
public class TimestampCacheFactory extends AbstractLogEnabled implements CacheFactory {
public class TimestampCacheFactory implements CacheFactory {
public TimestampCacheFactory() {
}

View File

@@ -30,15 +30,19 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiPredicate;
import java.util.function.Function;
import org.codehaus.plexus.logging.AbstractLogEnabled;
import java.util.stream.Stream;
import org.mvndaemon.mvnd.cache.Cache;
import org.mvndaemon.mvnd.cache.CacheFactory;
import org.mvndaemon.mvnd.cache.CacheRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A factory for {@link Cache} objects invalidating its entries based on events received from {@link WatchService}.
*/
public class WatchServiceCacheFactory extends AbstractLogEnabled implements CacheFactory {
public class WatchServiceCacheFactory implements CacheFactory {
private static final Logger LOG = LoggerFactory.getLogger(WatchServiceCacheFactory.class);
private final WatchService watchService;
@@ -84,9 +88,7 @@ public class WatchServiceCacheFactory extends AbstractLogEnabled implements Cach
private Registration register(Path key, Registration value) {
if (value == null) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Starting to watch path " + key);
}
LOG.debug("Starting to watch path {}", key);
try {
WatchEvent.Modifier[] mods;
try {
@@ -105,9 +107,7 @@ public class WatchServiceCacheFactory extends AbstractLogEnabled implements Cach
}
} else {
int cnt = value.count.incrementAndGet();
if (getLogger().isDebugEnabled()) {
getLogger().debug("Already " + cnt + " watchers for path " + key);
}
LOG.debug("Already {} watchers for path {}", cnt, key);
return value;
}
}
@@ -121,19 +121,13 @@ public class WatchServiceCacheFactory extends AbstractLogEnabled implements Cach
for (WatchEvent<?> event : watchKey.pollEvents()) {
final Path dir = entry.getKey();
WatchEvent.Kind<?> kind = event.kind();
if (getLogger().isDebugEnabled()) {
getLogger().debug("Got watcher event " + kind.name());
}
if (kind == StandardWatchEventKinds.ENTRY_DELETE || kind == StandardWatchEventKinds.ENTRY_MODIFY) {
final Path path = dir.resolve((Path) event.context());
LOG.debug("Got watcher event {} for file {}", kind.name(), path);
final List<CacheRecord> records = recordsByPath.remove(path);
if (getLogger().isDebugEnabled()) {
getLogger().debug("Records for path " + path + ": " + records);
}
LOG.debug("Records for path {}: {}", path, records);
if (records != null) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Invalidating recorder of path " + path);
}
LOG.debug("Invalidating records of path {}", path);
remove(records);
}
} else if (kind == StandardWatchEventKinds.OVERFLOW) {
@@ -143,9 +137,7 @@ public class WatchServiceCacheFactory extends AbstractLogEnabled implements Cach
Map.Entry<Path, List<CacheRecord>> en = it.next();
if (en.getKey().getParent().equals(dir)) {
it.remove();
if (getLogger().isDebugEnabled()) {
getLogger().debug("Invalidating recorder of path " + en.getKey());
}
LOG.debug("Invalidating records of path {}", en.getKey());
remove(en.getValue());
}
}
@@ -170,22 +162,16 @@ public class WatchServiceCacheFactory extends AbstractLogEnabled implements Cach
private Registration unregister(Path key, Registration value) {
if (value == null) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Already unwatchers for path " + key);
}
LOG.debug("Already stopped watching path {}", key);
return null;
} else {
final int cnt = value.count.decrementAndGet();
if (cnt <= 0) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Unwatching path " + key);
}
LOG.debug("Unwatching path {}", key);
value.watchKey.cancel();
return null;
} else {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Still " + cnt + " watchers for path " + key);
}
LOG.debug("Still " + cnt + " watchers for path {}", key);
return value;
}
}
@@ -210,7 +196,7 @@ public class WatchServiceCacheFactory extends AbstractLogEnabled implements Cach
@Override
public boolean contains(K key) {
return map.containsKey(key);
return get(key) != null;
}
@Override
@@ -221,7 +207,7 @@ public class WatchServiceCacheFactory extends AbstractLogEnabled implements Cach
@Override
public void put(K key, V value) {
add(value);
add(new WrappedCacheRecord<>(map, key, value));
map.put(key, value);
}
@@ -246,9 +232,33 @@ public class WatchServiceCacheFactory extends AbstractLogEnabled implements Cach
validateRecords();
return map.computeIfAbsent(key, k -> {
V v = mappingFunction.apply(k);
add(v);
add(new WrappedCacheRecord<>(map, k, v));
return v;
});
}
}
static class WrappedCacheRecord<K, V extends CacheRecord> implements CacheRecord {
private final Map<K, V> map;
private final K key;
private final V delegate;
public WrappedCacheRecord(Map<K, V> map, K key, V delegate) {
this.map = map;
this.key = key;
this.delegate = delegate;
}
@Override
public Stream<Path> getDependencyPaths() {
return delegate.getDependencyPaths();
}
@Override
public void invalidate() {
delegate.invalidate();
map.remove(key);
}
}
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mvndaemon.mvnd.cache.factory.impl;
package org.mvndaemon.mvnd.cache.impl;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -27,14 +27,23 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mvndaemon.mvnd.cache.Cache;
import org.mvndaemon.mvnd.cache.CacheRecord;
import org.mvndaemon.mvnd.cache.impl.DefaultCacheFactory;
import org.mvndaemon.mvnd.common.Os;
public class TimestampCacheFactoryTest {
public class CacheFactoryTest {
@Test
void putGet(@TempDir Path tempDir) throws IOException, InterruptedException {
void timestampCache(@TempDir Path tempDir) throws IOException, InterruptedException {
assertPutGet(tempDir, new TimestampCacheFactory().newCache(), 200);
}
@Test
void watchServiceCache(@TempDir Path tempDir) throws IOException, InterruptedException {
int asyncOpDelayMs = Os.current() == Os.MAC ? 2500 : 200;
assertPutGet(tempDir, new WatchServiceCacheFactory().newCache(), asyncOpDelayMs);
}
public void assertPutGet(Path tempDir, final Cache<String, CacheRecord> cache, int asyncOpDelayMs)
throws IOException, InterruptedException {
final Path file1 = tempDir.resolve("file1");
Files.write(file1, "content1".getBytes(StandardCharsets.UTF_8));
final SimpleCacheRecord record1 = new SimpleCacheRecord(file1);
@@ -43,8 +52,6 @@ public class TimestampCacheFactoryTest {
Files.write(file2, "content2".getBytes(StandardCharsets.UTF_8));
final SimpleCacheRecord record2 = new SimpleCacheRecord(file2);
final Cache<String, CacheRecord> cache = new DefaultCacheFactory().newCache();
final String k1 = "k1";
cache.put(k1, record1);
final String k2 = "k2";
@@ -58,9 +65,8 @@ public class TimestampCacheFactoryTest {
Assertions.assertFalse(record2.invalidated);
Files.write(file1, "content1.1".getBytes(StandardCharsets.UTF_8));
if (Os.current() == Os.WINDOWS) {
Thread.sleep(3000);
if (asyncOpDelayMs > 0) {
Thread.sleep(asyncOpDelayMs);
}
Assertions.assertFalse(cache.contains(k1));
@@ -71,8 +77,8 @@ public class TimestampCacheFactoryTest {
Assertions.assertFalse(record2.invalidated);
Files.delete(file2);
if (Os.current() == Os.WINDOWS) {
Thread.sleep(3000);
if (asyncOpDelayMs > 0) {
Thread.sleep(asyncOpDelayMs);
}
Assertions.assertFalse(cache.contains(k2));
Assertions.assertNull(cache.get(k2));
@@ -98,5 +104,10 @@ public class TimestampCacheFactoryTest {
this.invalidated = true;
}
@Override
public String toString() {
return "SimpleCacheRecord [paths=" + paths + ", invalidated=" + invalidated + "]";
}
}
}