Author: mreutegg
Date: Tue Oct 31 14:42:18 2017
New Revision: 1813888

URL: http://svn.apache.org/viewvc?rev=1813888&view=rev
Log:
OAK-6859: Schedule Revision GC in DocumentNodeStoreService

Modified:
    jackrabbit/oak/trunk/oak-store-document/pom.xml
    
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Configuration.java
    
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java
    
jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreServiceTest.java

Modified: jackrabbit/oak/trunk/oak-store-document/pom.xml
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-document/pom.xml?rev=1813888&r1=1813887&r2=1813888&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-store-document/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-store-document/pom.xml Tue Oct 31 14:42:18 2017
@@ -45,6 +45,7 @@
             <Export-Package>
               org.apache.jackrabbit.oak.plugins.document.spi,
             </Export-Package>
+            
<Embed-Dependency>quartz;inline=org/quartz/CronExpression*|org/quartz/ValueSet*</Embed-Dependency>
           </instructions>
         </configuration>
         <executions>
@@ -151,6 +152,12 @@
       <groupId>commons-io</groupId>
       <artifactId>commons-io</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.quartz-scheduler</groupId>
+      <artifactId>quartz</artifactId>
+      <version>2.2.3</version>
+      <scope>provided</scope>
+    </dependency>
 
     <!-- Optional dependencies for different persistence backends -->
     <dependency>

Modified: 
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Configuration.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Configuration.java?rev=1813888&r1=1813887&r2=1813888&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Configuration.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Configuration.java
 Tue Oct 31 14:42:18 2017
@@ -158,9 +158,22 @@ import static org.apache.jackrabbit.oak.
     long versionGcMaxAgeInSecs() default 
DocumentNodeStoreService.DEFAULT_VER_GC_MAX_AGE;
 
     @AttributeDefinition(
-            name = "Continuous Version GC Mode",
-            description = "Run Version GC continuously as a background task.")
-    boolean versionGCContinuous() default 
DocumentNodeStoreService.DEFAULT_CONTINUOUS_RGC;
+            name = "Version GC scheduler expression",
+            description = "A cron expression that defines when the Version GC 
is scheduled. " +
+                    "If this configuration entry is left empty, the default 
value depends on " +
+                    "the 'documentStoreType'. For 'MONGO' the default is to 
schedule a " +
+                    "run every five seconds (also known as Continuous Revision 
Garbage " +
+                    "collection). For 'RDB' the default is to schedule a run 
once a day " +
+                    "starting at 2 AM. The corresponding cron expression is '" 
+
+                    DocumentNodeStoreService.CLASSIC_RGC_EXPR + "'.")
+    String versionGCExpression() default "";
+
+    @AttributeDefinition(
+            name = "Time limit for a Version GC run (in sec)",
+            description = "A Version GC run is canceled after this number of 
seconds. " +
+                    "The default value is " + 
DocumentNodeStoreService.DEFAULT_RGC_TIME_LIMIT_SECS +
+                    " seconds.")
+    long versionGCTimeLimitInSecs() default 
DocumentNodeStoreService.DEFAULT_RGC_TIME_LIMIT_SECS;
 
     @AttributeDefinition(
             name = "Blob GC Max Age (in secs)",

Modified: 
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java?rev=1813888&r1=1813887&r2=1813888&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java
 Tue Oct 31 14:42:18 2017
@@ -35,10 +35,12 @@ import static org.apache.jackrabbit.oak.
 import static 
org.apache.jackrabbit.oak.plugins.document.NodeDocument.MODIFIED_IN_SECS_RESOLUTION;
 import static 
org.apache.jackrabbit.oak.spi.blob.osgi.SplitBlobStoreService.ONLY_STANDALONE_TARGET;
 import static 
org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils.registerMBean;
+import static 
org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils.scheduleWithFixedDelay;
 
 import java.io.ByteArrayInputStream;
 import java.io.Closeable;
 import java.io.IOException;
+import java.text.ParseException;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Dictionary;
@@ -57,7 +59,6 @@ import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
 import com.google.common.base.Strings;
 import com.google.common.base.Supplier;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.io.Closer;
 import com.google.common.util.concurrent.UncheckedExecutionException;
 import com.mongodb.MongoClientURI;
@@ -105,7 +106,6 @@ import org.apache.jackrabbit.oak.spi.whi
 import org.apache.jackrabbit.oak.spi.whiteboard.Registration;
 import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
 import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardExecutor;
-import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils;
 import org.apache.jackrabbit.oak.stats.StatisticsProvider;
 import org.apache.jackrabbit.oak.spi.descriptors.GenericDescriptors;
 import org.osgi.framework.Bundle;
@@ -121,6 +121,7 @@ import org.osgi.service.component.annota
 import org.osgi.service.component.annotations.ReferenceCardinality;
 import org.osgi.service.component.annotations.ReferencePolicy;
 import org.osgi.service.metatype.annotations.Designate;
+import org.quartz.CronExpression;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -141,7 +142,9 @@ public class DocumentNodeStoreService {
     static final boolean DEFAULT_SO_KEEP_ALIVE = false;
     static final String DEFAULT_PERSISTENT_CACHE = "cache,binary=0";
     static final String DEFAULT_JOURNAL_CACHE = "diff-cache";
-    static final boolean DEFAULT_CONTINUOUS_RGC = false;
+    public static final String CONTINUOUS_RGC_EXPR = "*/5 * * * * ?";
+    public static final String CLASSIC_RGC_EXPR = "0 0 2 * * ?";
+    public static final long DEFAULT_RGC_TIME_LIMIT_SECS = 3*60*60; // default 
is 3 hours
     private static final String PREFIX = "oak.documentstore.";
     private static final String DESCRIPTION = "oak.nodestore.description";
     static final long DEFAULT_JOURNAL_GC_INTERVAL_MILLIS = 5*60*1000; // 
default is 5min
@@ -201,7 +204,8 @@ public class DocumentNodeStoreService {
     private static final String PROP_JOURNAL_GC_MAX_AGE_MILLIS = 
"journalGCMaxAge";
     public static final String CUSTOM_BLOB_STORE = "customBlobStore";
     public static final String PROP_VER_GC_MAX_AGE = "versionGcMaxAgeInSecs";
-    public static final String PROP_VER_GC_CONTINUOUS = "versionGCContinuous";
+    public static final String PROP_VER_GC_EXPRESSION = "versionGCExpression";
+    public static final String PROP_VER_GC_TIME_LIMIT = 
"versionGCTimeLimitInSecs";
     public static final String PROP_REV_RECOVERY_INTERVAL = 
"lastRevRecoveryJobIntervalInSecs";
     public static final String PROP_BLOB_GC_MAX_AGE = "blobGcMaxAgeInSecs";
     public static final String PROP_BLOB_SNAPSHOT_INTERVAL = 
"blobTrackSnapshotIntervalInSecs";
@@ -545,7 +549,28 @@ public class DocumentNodeStoreService {
     }
 
     private boolean isContinuousRevisionGC() {
-        return toBoolean(prop(PROP_VER_GC_CONTINUOUS), DEFAULT_CONTINUOUS_RGC);
+        String expr = getVersionGCExpression();
+        String[] elements = expr.split("\\s");
+        // simple heuristic to determine if revision GC runs 'frequently'
+        return elements.length >= 6 && elements[1].equals("*");
+    }
+
+    private String getVersionGCExpression() {
+        String defaultExpr;
+        if (DocumentStoreType.fromString(config.documentStoreType()) == 
DocumentStoreType.MONGO) {
+            defaultExpr = CONTINUOUS_RGC_EXPR;
+        } else {
+            defaultExpr = CLASSIC_RGC_EXPR;
+        }
+        String expr = PropertiesUtil.toString(prop(PROP_VER_GC_EXPRESSION), 
defaultExpr);
+        // validate expression
+        try {
+            new CronExpression(expr);
+        } catch (ParseException e) {
+            log.warn("Invalid cron expression, falling back to default '" + 
defaultExpr + "'", e);
+            expr = defaultExpr;
+        }
+        return expr;
     }
 
     private void registerNodeStoreProvider(final NodeStore ns) {
@@ -799,19 +824,9 @@ public class DocumentNodeStoreService {
                     BlobGCMBean.TYPE, "Document node store blob garbage 
collection"));
         }
 
-        Runnable startGC = new RevisionGCJob(store, versionGcMaxAgeInSecs);
-        Runnable cancelGC = new Runnable() {
-            @Override
-            public void run() {
-                store.getVersionGarbageCollector().cancel();
-            }
-        };
-        Supplier<String> status = new Supplier<String>() {
-            @Override
-            public String get() {
-                return store.getVersionGarbageCollector().getStatus();
-            }
-        };
+        Runnable startGC = new RevisionGCJob(store, versionGcMaxAgeInSecs, 0);
+        Runnable cancelGC = () -> store.getVersionGarbageCollector().cancel();
+        Supplier<String> status = () -> 
store.getVersionGarbageCollector().getStatus();
         RevisionGC revisionGC = new RevisionGC(startGC, cancelGC, status, 
executor);
         addRegistration(registerMBean(whiteboard, RevisionGCMBean.class, 
revisionGC,
                 RevisionGCMBean.TYPE, "Document node store revision garbage 
collection"));
@@ -842,7 +857,7 @@ public class DocumentNodeStoreService {
     private void registerLastRevRecoveryJob(final DocumentNodeStore nodeStore) 
{
         long leaseTime = 
toLong(context.getProperties().get(PROP_REV_RECOVERY_INTERVAL),
                 ClusterNodeInfo.DEFAULT_LEASE_UPDATE_INTERVAL_MILLIS);
-        addRegistration(WhiteboardUtils.scheduleWithFixedDelay(whiteboard,
+        addRegistration(scheduleWithFixedDelay(whiteboard,
                 new LastRevRecoveryJob(nodeStore), 
TimeUnit.MILLISECONDS.toSeconds(leaseTime),
                 false/*runOnSingleClusterNode*/, true /*use dedicated pool*/));
     }
@@ -850,7 +865,7 @@ public class DocumentNodeStoreService {
     private void registerJournalGC(final DocumentNodeStore nodeStore) {
         long journalGCInterval = 
toLong(context.getProperties().get(PROP_JOURNAL_GC_INTERVAL_MILLIS),
                 DEFAULT_JOURNAL_GC_INTERVAL_MILLIS);
-        addRegistration(WhiteboardUtils.scheduleWithFixedDelay(whiteboard,
+        addRegistration(scheduleWithFixedDelay(whiteboard,
                 new JournalGCJob(nodeStore),
                 jobPropertiesFor(JournalGCJob.class),
                 TimeUnit.MILLISECONDS.toSeconds(journalGCInterval),
@@ -858,13 +873,15 @@ public class DocumentNodeStoreService {
     }
 
     private void registerVersionGCJob(final DocumentNodeStore nodeStore) {
-        if (isContinuousRevisionGC()) {
-            long versionGcMaxAgeInSecs = toLong(prop(PROP_VER_GC_MAX_AGE), 
DEFAULT_VER_GC_MAX_AGE);
-            addRegistration(WhiteboardUtils.scheduleWithFixedDelay(whiteboard,
-                    new RevisionGCJob(nodeStore, versionGcMaxAgeInSecs),
-                    jobPropertiesFor(RevisionGCJob.class),
-                    MODIFIED_IN_SECS_RESOLUTION, true, true));
-        }
+        String expr = getVersionGCExpression();
+        Map<String, Object> props = jobPropertiesFor(RevisionGCJob.class);
+        props.put("scheduler.expression", expr);
+        long versionGcMaxAgeInSecs = toLong(prop(PROP_VER_GC_MAX_AGE), 
DEFAULT_VER_GC_MAX_AGE);
+        long versionGCTimeLimitInSecs = toLong(prop(PROP_VER_GC_TIME_LIMIT), 
DEFAULT_RGC_TIME_LIMIT_SECS);
+        addRegistration(scheduleWithFixedDelay(whiteboard,
+                new RevisionGCJob(nodeStore, versionGcMaxAgeInSecs,
+                        versionGCTimeLimitInSecs),
+                props, MODIFIED_IN_SECS_RESOLUTION, true, true));
     }
 
     private String prop(String propName) {
@@ -940,23 +957,27 @@ public class DocumentNodeStoreService {
         private static final long LOG_INTERVAL = TimeUnit.HOURS.toMillis(1);
 
         private final DocumentNodeStore nodeStore;
-        private final long versionGcMaxAgeInSecs;
+        private final long versionGCMaxAgeInSecs;
+        private final long versionGCTimeLimitInSecs;
         private volatile Object lastResult = "";
         private long lastLogTime;
         private VersionGCStats stats;
 
         RevisionGCJob(DocumentNodeStore ns,
-                      long versionGcMaxAgeInSecs) {
+                      long versionGcMaxAgeInSecs,
+                      long versionGCTimeLimitInSecs) {
             this.nodeStore = ns;
-            this.versionGcMaxAgeInSecs = versionGcMaxAgeInSecs;
+            this.versionGCMaxAgeInSecs = versionGcMaxAgeInSecs;
+            this.versionGCTimeLimitInSecs = versionGCTimeLimitInSecs;
             resetStats();
         }
 
         @Override
         public void run() {
             VersionGarbageCollector gc = 
nodeStore.getVersionGarbageCollector();
+            gc.setOptions(gc.getOptions().withMaxDuration(TimeUnit.SECONDS, 
versionGCTimeLimitInSecs));
             try {
-                VersionGCStats s = gc.gc(versionGcMaxAgeInSecs, 
TimeUnit.SECONDS);
+                VersionGCStats s = gc.gc(versionGCMaxAgeInSecs, 
TimeUnit.SECONDS);
                 stats.addRun(s);
                 lastResult = s.toString();
             } catch (Exception e) {
@@ -1025,6 +1046,8 @@ public class DocumentNodeStoreService {
     }
 
     private static Map<String, Object> jobPropertiesFor(Class clazz) {
-        return ImmutableMap.of("scheduler.name", clazz.getName());
+        Map<String, Object> props = new HashMap<>();
+        props.put("scheduler.name", clazz.getName());
+        return props;
     }
 }

Modified: 
jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreServiceTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreServiceTest.java?rev=1813888&r1=1813887&r2=1813888&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreServiceTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreServiceTest.java
 Tue Oct 31 14:42:18 2017
@@ -40,7 +40,6 @@ import org.junit.rules.TemporaryFolder;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -153,21 +152,11 @@ public class DocumentNodeStoreServiceTes
     }
 
     @Test
-    public void nonContinuousRGCDefault() throws Exception {
+    public void continuousRGCDefault() throws Exception {
         Map<String, Object> config = newConfig(repoHome);
         MockOsgi.activate(service, context.bundleContext(), config);
-        for (Runnable r : context.getServices(Runnable.class, null)) {
-            assertNotEquals(r.getClass(), 
DocumentNodeStoreService.RevisionGCJob.class);
-        }
-    }
-
-    @Test
-    public void continuousRGC() throws Exception {
-        Map<String, Object> config = newConfig(repoHome);
-        config.put(DocumentNodeStoreService.PROP_VER_GC_CONTINUOUS, true);
-        MockOsgi.activate(service, context.bundleContext(), config);
         boolean jobScheduled = false;
-        for (Runnable r : context.getServices(Runnable.class, null)) {
+        for (Runnable r : context.getServices(Runnable.class, 
"(scheduler.expression=\\*/5 \\* \\* \\* \\* ?)")) {
             jobScheduled |= 
r.getClass().equals(DocumentNodeStoreService.RevisionGCJob.class);
         }
         assertTrue(jobScheduled);
@@ -176,7 +165,6 @@ public class DocumentNodeStoreServiceTes
     @Test
     public void continuousRGCJobAsSupplier() throws Exception {
         Map<String, Object> config = newConfig(repoHome);
-        config.put(DocumentNodeStoreService.PROP_VER_GC_CONTINUOUS, true);
         MockOsgi.activate(service, context.bundleContext(), config);
         Runnable rgcJob = null;
         for (Runnable r : context.getServices(Runnable.class, null)) {


Reply via email to