Skip to content

Commit

Permalink
Decouple cache invalidation criteria
Browse files Browse the repository at this point in the history
  • Loading branch information
dbmeneses committed Oct 1, 2015
1 parent 3badd47 commit 943e74f
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 141 deletions.
Expand Up @@ -21,11 +21,12 @@


import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;


import org.sonar.home.cache.TTLCacheInvalidation;

import org.sonar.batch.bootstrap.GlobalProperties; import org.sonar.batch.bootstrap.GlobalProperties;
import org.sonar.batch.bootstrap.MockHttpServer; import org.sonar.batch.bootstrap.MockHttpServer;
import org.sonar.batch.bootstrap.ServerClient; import org.sonar.batch.bootstrap.ServerClient;
import org.sonar.batch.bootstrap.Slf4jLogger; import org.sonar.batch.bootstrap.Slf4jLogger;

import org.sonar.batch.cache.WSLoader; import org.sonar.batch.cache.WSLoader;
import org.sonar.batch.cache.WSLoader.LoadStrategy; import org.sonar.batch.cache.WSLoader.LoadStrategy;
import org.junit.Rule; import org.junit.Rule;
Expand Down Expand Up @@ -57,7 +58,7 @@ public void setUp() throws Exception {
when(bootstrapProps.property("sonar.host.url")).thenReturn("http://localhost:" + server.getPort()); when(bootstrapProps.property("sonar.host.url")).thenReturn("http://localhost:" + server.getPort());


client = new ServerClient(bootstrapProps, new EnvironmentInformation("Junit", "4")); client = new ServerClient(bootstrapProps, new EnvironmentInformation("Junit", "4"));
cache = new PersistentCache(temp.getRoot().toPath(), 1000 * 60, new Slf4jLogger(), null); cache = new PersistentCache(temp.getRoot().toPath(), new TTLCacheInvalidation(100_000L), new Slf4jLogger(), null);
} }


@After @After
Expand Down
61 changes: 16 additions & 45 deletions sonar-home/src/main/java/org/sonar/home/cache/PersistentCache.java
Expand Up @@ -28,7 +28,6 @@
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;


Expand All @@ -44,20 +43,19 @@ public class PersistentCache {
private static final Charset ENCODING = StandardCharsets.UTF_8; private static final Charset ENCODING = StandardCharsets.UTF_8;
private static final String DIGEST_ALGO = "MD5"; private static final String DIGEST_ALGO = "MD5";


// eviction strategy is to expire entries after modification once a time duration has elapsed private final PersistentCacheInvalidation invalidation;
private final long defaultDurationToExpireMs;
private final Logger logger; private final Logger logger;
private final Path dir; private final Path dir;
private DirectoryLock lock; private DirectoryLock lock;


public PersistentCache(Path dir, long defaultDurationToExpireMs, Logger logger, DirectoryLock lock) { public PersistentCache(Path dir, PersistentCacheInvalidation invalidation, Logger logger, DirectoryLock lock) {
this.dir = dir; this.dir = dir;
this.defaultDurationToExpireMs = defaultDurationToExpireMs; this.invalidation = invalidation;
this.logger = logger; this.logger = logger;
this.lock = lock; this.lock = lock;


reconfigure(); reconfigure();
logger.debug("cache: " + dir + ", default expiration time (ms): " + defaultDurationToExpireMs); logger.debug("cache: " + dir);
} }


public synchronized void reconfigure() { public synchronized void reconfigure() {
Expand Down Expand Up @@ -149,7 +147,7 @@ public synchronized void clear() {
logger.info("cache: clearing"); logger.info("cache: clearing");
try { try {
lock(); lock();
deleteCacheEntries(new DirectoryClearFilter(lock.getFileLockName())); deleteCacheEntries(new DirectoryClearFilter());
} catch (IOException e) { } catch (IOException e) {
logger.error("Error clearing cache", e); logger.error("Error clearing cache", e);
} finally { } finally {
Expand All @@ -164,7 +162,7 @@ public synchronized void clean() {
logger.info("cache: cleaning"); logger.info("cache: cleaning");
try { try {
lock(); lock();
deleteCacheEntries(new DirectoryCleanFilter(defaultDurationToExpireMs, lock.getFileLockName())); deleteCacheEntries(new DirectoryCleanFilter());
} catch (IOException e) { } catch (IOException e) {
logger.error("Error cleaning cache", e); logger.error("Error cleaning cache", e);
} finally { } finally {
Expand Down Expand Up @@ -203,35 +201,21 @@ private void deleteCacheEntries(DirectoryStream.Filter<Path> filter) throws IOEx
} }
} }


private static class DirectoryClearFilter implements DirectoryStream.Filter<Path> { private class DirectoryClearFilter implements DirectoryStream.Filter<Path> {
private String lockFileName;

DirectoryClearFilter(String lockFileName) {
this.lockFileName = lockFileName;
}

@Override @Override
public boolean accept(Path entry) throws IOException { public boolean accept(Path entry) throws IOException {
return !lockFileName.equals(entry.getFileName().toString()); return !lock.getFileLockName().equals(entry.getFileName().toString());
} }
} }


private static class DirectoryCleanFilter implements DirectoryStream.Filter<Path> { private class DirectoryCleanFilter implements DirectoryStream.Filter<Path> {
private long defaultDurationToExpireMs;
private String lockFileName;

DirectoryCleanFilter(long defaultDurationToExpireMs, String lockFileName) {
this.defaultDurationToExpireMs = defaultDurationToExpireMs;
this.lockFileName = lockFileName;
}

@Override @Override
public boolean accept(Path entry) throws IOException { public boolean accept(Path entry) throws IOException {
if (lockFileName.equals(entry.getFileName().toString())) { if (lock.getFileLockName().equals(entry.getFileName().toString())) {
return false; return false;
} }


return isCacheEntryExpired(entry, defaultDurationToExpireMs); return invalidation.test(entry);
} }
} }


Expand All @@ -248,7 +232,7 @@ private void putCache(String key, InputStream stream) throws IOException {
private byte[] getCache(String key) throws IOException { private byte[] getCache(String key) throws IOException {
Path cachePath = getCacheEntryPath(key); Path cachePath = getCacheEntryPath(key);


if (!validateCacheEntry(cachePath, this.defaultDurationToExpireMs)) { if (!validateCacheEntry(cachePath)) {
return null; return null;
} }


Expand All @@ -258,7 +242,7 @@ private byte[] getCache(String key) throws IOException {
private Path getCacheCopy(String key) throws IOException { private Path getCacheCopy(String key) throws IOException {
Path cachePath = getCacheEntryPath(key); Path cachePath = getCacheEntryPath(key);


if (!validateCacheEntry(cachePath, this.defaultDurationToExpireMs)) { if (!validateCacheEntry(cachePath)) {
return null; return null;
} }


Expand All @@ -267,33 +251,20 @@ private Path getCacheCopy(String key) throws IOException {
return temp; return temp;
} }


private boolean validateCacheEntry(Path cacheEntryPath, long durationToExpireMs) throws IOException { private boolean validateCacheEntry(Path cacheEntryPath) throws IOException {
if (!Files.exists(cacheEntryPath)) { if (!Files.exists(cacheEntryPath)) {
return false; return false;
} }


if (isCacheEntryExpired(cacheEntryPath, durationToExpireMs)) { if (invalidation.test(cacheEntryPath)) {
logger.debug("cache: expiring entry"); logger.debug("cache: evicting entry");
Files.delete(cacheEntryPath); Files.delete(cacheEntryPath);
return false; return false;
} }


return true; return true;
} }


private static boolean isCacheEntryExpired(Path cacheEntryPath, long durationToExpireMs) throws IOException {
BasicFileAttributes attr = Files.readAttributes(cacheEntryPath, BasicFileAttributes.class);
long modTime = attr.lastModifiedTime().toMillis();

long age = System.currentTimeMillis() - modTime;

if (age > durationToExpireMs) {
return true;
}

return false;
}

private Path getCacheEntryPath(String key) { private Path getCacheEntryPath(String key) {
return dir.resolve(key); return dir.resolve(key);
} }
Expand Down
Expand Up @@ -55,20 +55,20 @@ public PersistentCacheBuilder setAreaForProject(String serverUrl, String serverV
.resolve(sanitizeFilename(projectKey)); .resolve(sanitizeFilename(projectKey));
return this; return this;
} }

public PersistentCacheBuilder setAreaForGlobal(String serverUrl) { public PersistentCacheBuilder setAreaForGlobal(String serverUrl) {
relativePath = Paths.get(sanitizeFilename(serverUrl)) relativePath = Paths.get(sanitizeFilename(serverUrl))
.resolve("global"); .resolve("global");
return this; return this;
} }

public PersistentCacheBuilder setAreaForLocalProject(String serverUrl, String serverVersion) { public PersistentCacheBuilder setAreaForLocalProject(String serverUrl, String serverVersion) {
relativePath = Paths.get(sanitizeFilename(serverUrl)) relativePath = Paths.get(sanitizeFilename(serverUrl))
.resolve(sanitizeFilename(serverVersion)) .resolve(sanitizeFilename(serverVersion))
.resolve("local"); .resolve("local");
return this; return this;
} }

public PersistentCacheBuilder setSonarHome(@Nullable Path p) { public PersistentCacheBuilder setSonarHome(@Nullable Path p) {
if (p != null) { if (p != null) {
this.cacheBasePath = p.resolve(DIR_NAME); this.cacheBasePath = p.resolve(DIR_NAME);
Expand All @@ -77,15 +77,16 @@ public PersistentCacheBuilder setSonarHome(@Nullable Path p) {
} }


public PersistentCache build() { public PersistentCache build() {
if(relativePath == null) { if (relativePath == null) {
throw new IllegalStateException("area must be set before building"); throw new IllegalStateException("area must be set before building");
} }
if (cacheBasePath == null) { if (cacheBasePath == null) {
setSonarHome(findHome()); setSonarHome(findHome());
} }
Path cachePath = cacheBasePath.resolve(relativePath); Path cachePath = cacheBasePath.resolve(relativePath);
DirectoryLock lock = new DirectoryLock(cacheBasePath, logger); DirectoryLock lock = new DirectoryLock(cacheBasePath, logger);
return new PersistentCache(cachePath, DEFAULT_EXPIRE_DURATION, logger, lock); PersistentCacheInvalidation criteria = new TTLCacheInvalidation(DEFAULT_EXPIRE_DURATION);
return new PersistentCache(cachePath, criteria, logger, lock);
} }


private static Path findHome() { private static Path findHome() {
Expand Down
@@ -0,0 +1,27 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* SonarQube is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.home.cache;

import java.io.IOException;
import java.nio.file.Path;

public interface PersistentCacheInvalidation {
boolean test(Path cacheEntryPath) throws IOException;
}
@@ -0,0 +1,48 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* SonarQube is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.home.cache;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;

public class TTLCacheInvalidation implements PersistentCacheInvalidation {
private final long durationToExpireMs;

public TTLCacheInvalidation(long durationToExpireMs) {
this.durationToExpireMs = durationToExpireMs;
}

@Override
public boolean test(Path cacheEntryPath) throws IOException {
BasicFileAttributes attr = Files.readAttributes(cacheEntryPath, BasicFileAttributes.class);
long modTime = attr.lastModifiedTime().toMillis();

long age = System.currentTimeMillis() - modTime;

if (age > durationToExpireMs) {
return true;
}

return false;
}

}

This file was deleted.

0 comments on commit 943e74f

Please sign in to comment.