Poll the filesystem for new events
In a multi-primary environment events may be created and written
to the filestore from any primary. Without a notification system,
events written by other servers might only be seen by the current
server when the current server creates a new event. If the traffic
on the current server is low, this delay may be significant and
unacceptable.
In order to get events from other primaries output in a timely manner,
poll the filesystem to recongnize when new events may have been
written to the filestore and output them. This makes events timely
in a multi-primary environment without any notification system, and
makes it more robustly timely in systems with notifications.
Change-Id: Ic474f3ca639cc839b555f1ee82c97cc3e5882a89
diff --git a/src/main/java/com/googlesource/gerrit/plugins/events/FileSystemEventBroker.java b/src/main/java/com/googlesource/gerrit/plugins/events/FileSystemEventBroker.java
index ef73a36..f3f5855 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/events/FileSystemEventBroker.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/events/FileSystemEventBroker.java
@@ -125,7 +125,7 @@
}
}
- protected synchronized void sendAllPendingEvents() throws PermissionBackendException {
+ public synchronized void sendAllPendingEvents() throws PermissionBackendException {
try {
long current = store.getHead();
while (lastSent < current) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/events/Module.java b/src/main/java/com/googlesource/gerrit/plugins/events/Module.java
index c0e1271..7774a38 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/events/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/events/Module.java
@@ -14,17 +14,38 @@
package com.googlesource.gerrit.plugins.events;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicSet;
+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.events.EventDispatcher;
-import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.events.fsstore.FsListener.FsLifecycleListener;
import com.googlesource.gerrit.plugins.events.fsstore.FsStore;
-public class Module extends AbstractModule {
+public class Module extends LifecycleModule {
+ private static final int DEFAULT_POLLING_INTERVAL = 0;
+
+ @Provides
+ @Singleton
+ @PollingInterval
+ protected Long getCleanupInterval(PluginConfigFactory cfg, @PluginName String pluginName) {
+ String fromConfig =
+ Strings.nullToEmpty(cfg.getFromGerritConfig(pluginName).getString("pollingInterval"));
+ return SECONDS.toMillis(ConfigUtil.getTimeUnit(fromConfig, DEFAULT_POLLING_INTERVAL, SECONDS));
+ }
+
@Override
protected void configure() {
DynamicSet.setOf(binder(), StreamEventListener.class);
bind(EventStore.class).to(FsStore.class);
DynamicItem.bind(binder(), EventDispatcher.class).to(FileSystemEventBroker.class);
+ listener().to(FsLifecycleListener.class);
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/events/PollingInterval.java b/src/main/java/com/googlesource/gerrit/plugins/events/PollingInterval.java
new file mode 100644
index 0000000..bf05885
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/events/PollingInterval.java
@@ -0,0 +1,24 @@
+// Copyright (C) 2017 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.events;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+import java.lang.annotation.Retention;
+
+@Retention(RUNTIME)
+@BindingAnnotation
+public @interface PollingInterval {}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/events/fsstore/FsListener.java b/src/main/java/com/googlesource/gerrit/plugins/events/fsstore/FsListener.java
new file mode 100644
index 0000000..7ed0c1b
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/events/fsstore/FsListener.java
@@ -0,0 +1,84 @@
+// Copyright (C) 2017 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.events.fsstore;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.server.events.EventDispatcher;
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.events.FileSystemEventBroker;
+import com.googlesource.gerrit.plugins.events.PollingInterval;
+import java.util.concurrent.ScheduledFuture;
+
+@Singleton
+public class FsListener implements Runnable {
+ public static class FsLifecycleListener implements LifecycleListener {
+ protected final WorkQueue queue;
+ protected final long pollingInterval;
+ protected final FileSystemEventBroker broker;
+ protected ScheduledFuture<?> future;
+
+ @Inject
+ protected FsLifecycleListener(
+ WorkQueue queue, @PollingInterval long pollingInterval, EventDispatcher dispatcher) {
+ this.queue = queue;
+ this.pollingInterval = pollingInterval;
+ this.broker = (FileSystemEventBroker) dispatcher;
+ }
+
+ @Override
+ public void start() {
+ if (pollingInterval > 0) {
+ ScheduledFuture<?> future =
+ queue
+ .getDefaultQueue()
+ .scheduleAtFixedRate(
+ new FsListener(broker), pollingInterval, pollingInterval, MILLISECONDS);
+ }
+ }
+
+ @Override
+ public void stop() {
+ if (future != null) {
+ future.cancel(true);
+ }
+ }
+ }
+
+ protected final FileSystemEventBroker broker;
+
+ @Inject
+ protected FsListener(FileSystemEventBroker broker) {
+ this.broker = broker;
+ }
+
+ @Override
+ public void run() {
+ try {
+ broker.sendAllPendingEvents();
+ } catch (PermissionBackendException e) {
+ // Ignore
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Events FS Polling Listener";
+ }
+}
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
new file mode 100644
index 0000000..2fef2db
--- /dev/null
+++ b/src/main/resources/Documentation/config.md
@@ -0,0 +1,40 @@
+@PLUGIN@ Configuration
+======================
+
+In a multi-primary environment events may be created and written
+to the filestore from any primary. Without a notification system,
+events written by other servers might only be seen by the current
+server when the current server creates a new event. If the traffic
+on the current server is low, this delay may be significant and
+unacceptable.
+
+In order to get events from other primaries output in a timely manner,
+the @PLUGIN@ plugin can be configured to poll and recongnize when
+new events may have been written to the filestore and output them.
+
+Reload the plugin on each primary for the changes to take effect.
+
+The polling frequency can be specified in the configuration.
+For example:
+
+```
+ [plugin "@PLUGIN@"]
+ pollingInterval = 3s
+```
+
+causes polling to be done every 3 seconds.
+
+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, `seconds` is assumed.
+
+If 'pollingInterval' is not present in the configuration, polling
+will not be enabled.