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.