Periodically clean outdated session cache files
When a user logs out, the corresponding web session cache file is
deleted. When the web session expires, however, the file remains in the
cache directory. In a busy Gerrit server using this plugin, the size of
the cache directory can reach hundreds of megabytes within a few months.
Remove outdated session cache files in a periodic way. Cache files are
considered as outdated when its expiration date has passed.
Change-Id: Id195f2b04ef4b323ce5c5a1c024e62ebeea39720
diff --git a/BUCK b/BUCK
index 7f1487c..2260abb 100644
--- a/BUCK
+++ b/BUCK
@@ -14,6 +14,7 @@
resources = RESOURCES,
manifest_entries = [
'Gerrit-PluginName: websession-flatfile',
+ 'Gerrit-Module: com.googlesource.gerrit.plugins.websession.flatfile.Module',
'Gerrit-HttpModule: com.googlesource.gerrit.plugins.websession.flatfile.FlatFileWebSession$Module',
'Implementation-Title: Flat file WebSession',
'Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/websession-flatfile',
diff --git a/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/CleanupInterval.java b/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/CleanupInterval.java
new file mode 100644
index 0000000..9874881
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/CleanupInterval.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// 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 com.googlesource.gerrit.plugins.websession.flatfile;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+
+import java.lang.annotation.Retention;
+
+@Retention(RUNTIME)
+@BindingAnnotation
+public @interface CleanupInterval {
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSession.java b/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSession.java
index 75716eb..e9a2b70 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSession.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSession.java
@@ -14,7 +14,6 @@
package com.googlesource.gerrit.plugins.websession.flatfile;
-import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.annotations.RootRelative;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.httpd.CacheBasedWebSession;
@@ -23,19 +22,12 @@
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.IdentifiedUser.RequestFactory;
import com.google.gerrit.server.config.AuthConfig;
-import com.google.gerrit.server.config.PluginConfigFactory;
-import com.google.gerrit.server.config.SitePaths;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import com.google.inject.Provides;
-import com.google.inject.Singleton;
import com.google.inject.servlet.RequestScoped;
import com.google.inject.servlet.ServletScopes;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -49,15 +41,6 @@
DynamicItem.bind(binder(), WebSession.class)
.to(FlatFileWebSession.class).in(RequestScoped.class);
}
-
- @Provides
- @Singleton
- @WebSessionDir
- Path getWebSessionDir(SitePaths site, PluginConfigFactory cfg,
- @PluginName String pluginName) {
- return Paths.get(cfg.getFromGerritConfig(pluginName).getString(
- "directory", site.site_path + "/websessions"));
- }
}
@Inject
diff --git a/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSessionCache.java b/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSessionCache.java
index 8d12bb2..50acdf0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSessionCache.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSessionCache.java
@@ -23,6 +23,7 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -80,7 +81,13 @@
@Override
public void cleanUp() {
- // do nothing
+ for (Path path : listFiles()) {
+ Val val = readFile(path);
+ DateTime expires = new DateTime(val.getExpiresAt());
+ if (expires.isBefore(new DateTime())) {
+ deleteFile(path);
+ }
+ }
}
@Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSessionCacheCleaner.java b/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSessionCacheCleaner.java
new file mode 100644
index 0000000..c6c89a6
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSessionCacheCleaner.java
@@ -0,0 +1,68 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// 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 com.googlesource.gerrit.plugins.websession.flatfile;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.inject.Inject;
+
+class FlatFileWebSessionCacheCleaner implements Runnable {
+
+ static class CleanerLifecycle implements LifecycleListener {
+ private static final int INITIAL_DELAY_MS = 1000;
+ private final WorkQueue queue;
+ private final FlatFileWebSessionCacheCleaner cleaner;
+ private final long cleanupInterval;
+
+ @Inject
+ CleanerLifecycle(
+ WorkQueue queue,
+ FlatFileWebSessionCacheCleaner cleaner,
+ @CleanupInterval long cleanupInterval) {
+ this.queue = queue;
+ this.cleaner = cleaner;
+ this.cleanupInterval = cleanupInterval;
+ }
+
+ @Override
+ public void start() {
+ queue.getDefaultQueue().scheduleAtFixedRate(cleaner, INITIAL_DELAY_MS,
+ cleanupInterval, MILLISECONDS);
+ }
+
+ @Override
+ public void stop() {
+ }
+ }
+
+ private FlatFileWebSessionCache flatFileWebSessionCache;
+
+ @Inject
+ FlatFileWebSessionCacheCleaner(FlatFileWebSessionCache flatFileWebSessionCache) {
+ this.flatFileWebSessionCache = flatFileWebSessionCache;
+ }
+
+ @Override
+ public void run() {
+ flatFileWebSessionCache.cleanUp();
+ }
+
+ @Override
+ public String toString() {
+ return "FlatFile WebSession Cleaner";
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/Module.java b/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/Module.java
new file mode 100644
index 0000000..a5ea0c3
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/websession/flatfile/Module.java
@@ -0,0 +1,61 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// 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 com.googlesource.gerrit.plugins.websession.flatfile;
+
+import static java.util.concurrent.TimeUnit.HOURS;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.server.config.ConfigUtil;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+
+import com.googlesource.gerrit.plugins.websession.flatfile.FlatFileWebSessionCacheCleaner.CleanerLifecycle;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class Module extends LifecycleModule {
+
+ private static final int DEFAULT_CLEANUP_INTERVAL = 24;
+
+ @Override
+ protected void configure() {
+ listener().to(CleanerLifecycle.class);
+ }
+
+ @Provides
+ @Singleton
+ @WebSessionDir
+ Path getWebSessionDir(SitePaths site, PluginConfigFactory cfg,
+ @PluginName String pluginName) {
+ return Paths.get(cfg.getFromGerritConfig(pluginName).getString("directory",
+ site.site_path + "/websessions"));
+ }
+
+ @Provides
+ @Singleton
+ @CleanupInterval
+ Long getCleanupInterval(PluginConfigFactory cfg, @PluginName String pluginName) {
+ String fromConfig =
+ Strings.nullToEmpty(cfg.getFromGerritConfig(pluginName).getString(
+ "cleanupInterval"));
+ return HOURS.toMillis(ConfigUtil.getTimeUnit(fromConfig,
+ DEFAULT_CLEANUP_INTERVAL, HOURS));
+ }
+}
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 09a49d5..cc5e71f 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -8,8 +8,10 @@
main Gerrit config file: $site_dir/etc/gerrit.config. This
location defaults to $site_dir/websessions.
+```
[plugin "@PLUGIN@"]
directory = <disk_cache_directory>
+
# NOTE: <disk_cache_directory> can be any location on the
# shared filesystem that can be accessed by all servers,
# and in which rename operations are atomic and allow
@@ -19,6 +21,33 @@
Reload the plugin on each master for the changes to take
effect.
+The plugin periodically cleans up the cache directory, deleting
+files corresponding to expired sessions. The frequency of this
+operation can be specified in the configuration. For example:
+
+```
+ [plugin "@PLUGIN@"]
+ cleanupInterval = 1h
+```
+
+indicates the cleanup operation to be triggered every hour.
+
+Values should use common time unit suffixes to express their setting:
+
+* s, sec, second, seconds
+* m, min, minute, minutes
+* h, hr, hour, hours
+* d, day, days
+* w, week, weeks (`1 week` is treated as `7 days`)
+* mon, month, months (`1 month` is treated as `30 days`)
+* y, year, years (`1 year` is treated as `365 days`)
+
+If a time unit suffix is not specified, `hours` is assumed.
+
+If 'cleanupInterval' is not present in the configuration, the
+cleanup operation is triggered every 24 hours.
+
+
SEE ALSO
--------
diff --git a/src/test/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSessionCacheTest.java b/src/test/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSessionCacheTest.java
index 1eb7200..038135a 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSessionCacheTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/websession/flatfile/FlatFileWebSessionCacheTest.java
@@ -84,11 +84,19 @@
}
@Test
+ public void cleanUpTest() throws Exception {
+ loadExistingKeyToCacheDir();
+ flatFileWebSessionCache.cleanUp();
+ assertThat(isDirEmpty(dir)).isTrue();
+ }
+
+ @Test
public void getAllPresentTest() throws Exception {
Files.createFile(dir.resolve(key));
loadExistingKeyToCacheDir();
List<String> keys = Arrays.asList(new String[] {key, existingKey});
- assertThat(flatFileWebSessionCache.getAllPresent(keys)).containsKey(existingKey);
+ assertThat(flatFileWebSessionCache.getAllPresent(keys))
+ .containsKey(existingKey);
}
@Test
@@ -116,13 +124,15 @@
return null;
}
}
- assertThat(flatFileWebSessionCache.get(existingKey, new ValueLoader())).isNull();
+ assertThat(flatFileWebSessionCache.get(existingKey, new ValueLoader()))
+ .isNull();
loadExistingKeyToCacheDir();
- assertThat(flatFileWebSessionCache.get(existingKey, new ValueLoader())).isNotNull();
+ assertThat(flatFileWebSessionCache.get(existingKey, new ValueLoader()))
+ .isNotNull();
}
- @Test(expected=ExecutionException.class)
+ @Test(expected = ExecutionException.class)
public void getTestCallableThrowsException() throws Exception {
class ValueLoader implements Callable<Val> {
@Override
@@ -130,7 +140,8 @@
throw new Exception();
}
}
- assertThat(flatFileWebSessionCache.get(existingKey, new ValueLoader())).isNull();
+ assertThat(flatFileWebSessionCache.get(existingKey, new ValueLoader()))
+ .isNull();
}
@Test
@@ -166,10 +177,10 @@
@Test
public void putTest() throws Exception {
loadExistingKeyToCacheDir();
- Val val = flatFileWebSessionCache.getIfPresent(existingKey);
- String newKey = "abcde12345";
- flatFileWebSessionCache.put(newKey, val);
- assertThat(flatFileWebSessionCache.getIfPresent(newKey)).isNotNull();
+ Val val = flatFileWebSessionCache.getIfPresent(existingKey);
+ String newKey = "abcde12345";
+ flatFileWebSessionCache.put(newKey, val);
+ assertThat(flatFileWebSessionCache.getIfPresent(newKey)).isNotNull();
}
@Test