Skip to content

Commit

Permalink
#19725 debounces graphql cache evictions and syncronizes the cache re…
Browse files Browse the repository at this point in the history
…build
  • Loading branch information
wezell committed Dec 22, 2020
1 parent 2127c37 commit 2da8004
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 5 deletions.
64 changes: 64 additions & 0 deletions dotCMS/src/main/java/com/dotcms/concurrent/Debouncer.java
@@ -0,0 +1,64 @@
package com.dotcms.concurrent;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.dotmarketing.util.Logger;
import com.google.common.annotations.VisibleForTesting;

/**
* Usage:
*
* final Debouncer debouncer = new Debouncer();
* debouncer.debounce("myRunnableKey", new Runnable() {()->{runnable..} , 300, TimeUnit.MILLISECONDS);
*/
public class Debouncer {

@VisibleForTesting
Debouncer(Long runCount) {
this.runCount = runCount;
}

public Debouncer() {

}

@VisibleForTesting
Long runCount = null;

private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final ConcurrentHashMap<String, Future<?>> delayedMap = new ConcurrentHashMap<>();

/**
* Debounces {@code callable} by {@code delay}, i.e., schedules it to be executed after
* {@code delay}, or cancels its execution if the method is called with the same key within the
* {@code delay} again.
*/
public void debounce(final String key, final Runnable runnable, final long delay, final TimeUnit unit) {
final Future<?> prev = delayedMap.put(key, scheduler.schedule(new Runnable() {
@Override
public void run() {
try {
if (null != runCount) {
++runCount;
Logger.info(Debouncer.class, () -> "Debouncer has run : " + runCount + " times");
}
Logger.info(Debouncer.class, () -> "Debouncer Runnning : " + key + " after " + delay + " " + unit);

runnable.run();
} finally {
delayedMap.remove(key);
}
}
}, delay, unit));
if (prev != null) {
prev.cancel(true);
}
}

public void shutdown() {
scheduler.shutdownNow();
}
}
Expand Up @@ -6,7 +6,7 @@
import static graphql.schema.GraphQLList.list;
import static graphql.schema.GraphQLNonNull.nonNull;
import static graphql.schema.GraphQLObjectType.newObject;

import com.dotcms.concurrent.Debouncer;
import com.dotcms.graphql.InterfaceType;
import com.dotcms.graphql.datafetcher.ContentletDataFetcher;
import com.dotcms.util.LogTime;
Expand All @@ -31,6 +31,7 @@
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;

public class GraphqlAPIImpl implements GraphqlAPI {

Expand Down Expand Up @@ -71,23 +72,43 @@ public GraphqlAPIImpl() {
public GraphQLSchema getSchema() throws DotDataException {
Optional<GraphQLSchema> schema = schemaCache.getSchema();

if(!schema.isPresent()) {
if(schema.isPresent()) {
return schema.get();
}
synchronized (this) {
schema = schemaCache.getSchema();
if(schema.isPresent()) {
return schema.get();
}
final GraphQLSchema generatedSchema = generateSchema();
schemaCache.putSchema(generatedSchema);
return generatedSchema;
} else {
return schema.get();
}

}

final Debouncer debouncer = new Debouncer();


/**
* Nullifies the schema so it is regenerated next time it is fetched
* This method is debounced for 30 seconds to prevent overloading when
* content types are saved.
*/
@Override
public void invalidateSchema() {
schemaCache.removeSchema();

debouncer.debounce("invalidateGraphSchema", new Runnable() {
@Override
public void run() {
schemaCache.removeSchema();
}
}, Config.getIntProperty("GRAPHQL_SCHEMA_DEBOUNCE_DELAY", 30), TimeUnit.SECONDS);;


}


@Override
public void printSchema() {
if (Config.getBooleanProperty("GRAPHQL_PRINT_SCHEMA", false)) {
Expand Down
38 changes: 38 additions & 0 deletions dotCMS/src/test/java/com/dotcms/concurrent/DebouncerTest.java
@@ -0,0 +1,38 @@
package com.dotcms.concurrent;

import static org.junit.Assert.*;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import com.dotmarketing.util.Logger;

public class DebouncerTest {





@Test
public void test() throws InterruptedException {
Debouncer debouncer = new Debouncer(0L);

// debounce for 10 seconds
debouncer.debounce("testingKey", ()->{Logger.info("DebouncerTest","running debouncer test" );}, 5, TimeUnit.SECONDS);
debouncer.debounce("testingKey", ()->{Logger.info("DebouncerTest","running debouncer test" );}, 5, TimeUnit.SECONDS);
debouncer.debounce("testingKey", ()->{Logger.info("DebouncerTest","running debouncer test" );}, 5, TimeUnit.SECONDS);
debouncer.debounce("testingKey", ()->{Logger.info("DebouncerTest","running debouncer test" );}, 5, TimeUnit.SECONDS);
debouncer.debounce("testingKey", ()->{Logger.info("DebouncerTest","running debouncer test" );}, 5, TimeUnit.SECONDS);
debouncer.debounce("testingKey", ()->{Logger.info("DebouncerTest","running debouncer test" );}, 5, TimeUnit.SECONDS);
debouncer.debounce("testingKey", ()->{Logger.info("DebouncerTest","running debouncer test" );}, 5, TimeUnit.SECONDS);

Thread.sleep(7*1000);


assertTrue("assert that we have only run once", debouncer.runCount==1);





}

}

0 comments on commit 2da8004

Please sign in to comment.