mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-17 01:31:01 +00:00
Merge branch 'master' into ubilabs--rcapp--develop
This commit is contained in:
commit
03924c1df2
7 files changed
+413
-168
No files matched your search
+126
@@ -8,12 +8,18 @@ import static org.junit.Assert.assertTrue;
|
||||
import java.util.HashSet;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.BrokenBarrierException;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.concurrent.CyclicBarrier;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sse.util.SmartFutureCache;
|
||||
import com.sap.sse.util.SmartFutureCache.CacheUpdater;
|
||||
import com.sap.sse.util.SmartFutureCache.EmptyUpdateInterval;
|
||||
import com.sap.sse.util.SmartFutureCache.UpdateInterval;
|
||||
|
||||
@@ -247,4 +253,124 @@ public class SmartFutureCacheTest {
|
||||
System.out.println("Tasks re-used: "+sfc.getSmartFutureCacheTaskReuseCounter());
|
||||
assertEquals(updatesTriggeredFor.size(), updateWasCalled.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIfNoNewFuturesAreRunForTheSameKeyWhileCurrentTaskIsSleeping() throws InterruptedException, BrokenBarrierException {
|
||||
final AtomicInteger callCounter = new AtomicInteger(0);
|
||||
CyclicBarrier barrier = new CyclicBarrier(2);
|
||||
CacheUpdater<Integer, Integer, EmptyUpdateInterval> cacheUpdater = new CacheUpdater<Integer, Integer, SmartFutureCache.EmptyUpdateInterval>() {
|
||||
|
||||
@Override
|
||||
public Integer computeCacheUpdate(Integer key, EmptyUpdateInterval updateInterval) throws Exception {
|
||||
synchronized (callCounter) {
|
||||
if (key == 1) {
|
||||
callCounter.incrementAndGet();
|
||||
}
|
||||
}
|
||||
barrier.await();
|
||||
barrier.await();
|
||||
barrier.await();
|
||||
//For this test case value will be same as key, when updated.
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer provideNewCacheValue(Integer key, Integer oldCacheValue, Integer computedCacheUpdate,
|
||||
EmptyUpdateInterval updateInterval) {
|
||||
return computedCacheUpdate;
|
||||
}
|
||||
};
|
||||
SmartFutureCache<Integer, Integer, EmptyUpdateInterval> testCache = new SmartFutureCache<Integer, Integer, SmartFutureCache.EmptyUpdateInterval>(
|
||||
cacheUpdater, "SmartFutureTestCacheLock");
|
||||
testCache.triggerUpdate(1, null);
|
||||
barrier.await();
|
||||
assertEquals(1, callCounter.get());
|
||||
testCache.triggerUpdate(1, null);
|
||||
barrier.await();
|
||||
// Counter should still be one here, since first future is still sleeping at this point
|
||||
assertEquals(1, callCounter.get());
|
||||
barrier.await();
|
||||
|
||||
// Now the first future should be done, and the second update should have been called, so the counter should be 2
|
||||
barrier.await();
|
||||
assertEquals(2, callCounter.get());
|
||||
barrier.await();
|
||||
barrier.await();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTriggerAndGetWithWaitForLatestWithRunningFutureOnSuspendedCache() throws InterruptedException, BrokenBarrierException {
|
||||
final AtomicInteger callCounter = new AtomicInteger(0);
|
||||
CyclicBarrier barrier = new CyclicBarrier(2);
|
||||
CacheUpdater<Integer, Integer, EmptyUpdateInterval> cacheUpdater = new CacheUpdater<Integer, Integer, SmartFutureCache.EmptyUpdateInterval>() {
|
||||
|
||||
@Override
|
||||
public Integer computeCacheUpdate(Integer key, EmptyUpdateInterval updateInterval) throws Exception {
|
||||
synchronized (callCounter) {
|
||||
if (key == 1) {
|
||||
callCounter.incrementAndGet();
|
||||
}
|
||||
}
|
||||
barrier.await();
|
||||
barrier.await();
|
||||
barrier.await();
|
||||
return callCounter.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer provideNewCacheValue(Integer key, Integer oldCacheValue, Integer computedCacheUpdate,
|
||||
EmptyUpdateInterval updateInterval) {
|
||||
return computedCacheUpdate;
|
||||
}
|
||||
};
|
||||
SmartFutureCache<Integer, Integer, EmptyUpdateInterval> testCache = new SmartFutureCache<Integer, Integer, SmartFutureCache.EmptyUpdateInterval>(
|
||||
cacheUpdater, "SmartFutureTestCacheLock");
|
||||
testCache.triggerUpdate(1, null);
|
||||
barrier.await();
|
||||
assertEquals(1, callCounter.get());
|
||||
testCache.triggerUpdate(1, null);
|
||||
testCache.suspend();
|
||||
|
||||
CyclicBarrier getResultBarrier = new CyclicBarrier(2);
|
||||
Thread thread = new Thread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
//FIXME find a way to guarantee that get is called, while the first computeUpdate Future is still running
|
||||
int result = testCache.get(1, true);
|
||||
assertEquals(2, result);
|
||||
try {
|
||||
getResultBarrier.await();
|
||||
} catch (InterruptedException | BrokenBarrierException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
barrier.await();
|
||||
|
||||
barrier.await();
|
||||
|
||||
barrier.await();
|
||||
assertEquals(2, callCounter.get());
|
||||
//Trigger again. This update should not retrigger add another future, since cache is disabled and no
|
||||
// get with waitForLatest is called afterwards
|
||||
testCache.triggerUpdate(1, null);
|
||||
barrier.await();
|
||||
barrier.await();
|
||||
//Wait for get Thread to finish with getting and asserting
|
||||
getResultBarrier.await();
|
||||
assertEquals(2, callCounter.get());
|
||||
//Check if no compute Thread is waiting at barrier.
|
||||
boolean timeOut = false;
|
||||
try {
|
||||
barrier.await(10, TimeUnit.MILLISECONDS);
|
||||
} catch (TimeoutException e) {
|
||||
timeOut = true;
|
||||
}
|
||||
assertTrue(timeOut);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -126,8 +126,8 @@ public class QuickRanksLiveCache {
|
||||
trackedRace.addListener(new Listener(raceIdentifier)); // register for all changes that may affect the quick ranks
|
||||
cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE);
|
||||
}
|
||||
result = cache.get(raceIdentifier, /* wait for latest result */ true);
|
||||
}
|
||||
result = cache.get(raceIdentifier, /* wait for latest result */ true);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
@@ -1,12 +1,12 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||
org.eclipse.jdt.core.compiler.compliance=1.7
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.source=1.8
|
||||
org.eclipse.jdt.core.compiler.source=1.7
|
||||
@@ -4,7 +4,7 @@ Bundle-Name: Shared
|
||||
Bundle-SymbolicName: com.sap.sse.datamining.shared
|
||||
Bundle-Version: 1.0.0.qualifier
|
||||
Bundle-Vendor: SAP
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
|
||||
Export-Package: com.sap.sse.datamining.shared,
|
||||
com.sap.sse.datamining.shared.annotations,
|
||||
com.sap.sse.datamining.shared.components,
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
|
||||
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
|
||||
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
|
||||
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="true"/>
|
||||
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9875"/>
|
||||
<booleanAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT_AUTO" value="true"/>
|
||||
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
|
||||
<listEntry value="com.sap.sse.security.ui.UserManagement"/>
|
||||
</listAttribute>
|
||||
<booleanAttribute key="com.google.gwt.eclipse.core.SUPERDEVMODE_ENABLED" value="true"/>
|
||||
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/security/ui/Login.html"/>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/com.sap.sse.security.ui"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="4"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry containerPath="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" javaProject="com.sap.sse.security.ui" path="1" type="4"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.security.ui/src/main/resources" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.security.ui/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.googlecode.java-diff-utils/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/org.json.simple/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.operationaltransformation/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.replication/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/org.moxieapps.gwt.highcharts/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.datamining.shared/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-user.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath"> <memento exportedEntriesOnly="false" project="com.sap.sse.security.ui"/> </runtimeClasspathEntry> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.security/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt.adminconsole/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.security.common/src" path="3" type="2"/> "/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-superDevMode -incremental -war "${project_loc:com.sap.sse.security.ui}" -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -logLevel INFO -codeServerPort auto -startupUrl /security/ui/Login.html com.sap.sse.security.ui.Login -startupUrl /security/ui/EditProfile.html com.sap.sse.security.ui.EditProfile -startupUrl /security/ui/EmailValidation.html com.sap.sse.security.ui.EmailValidation -startupUrl /security/ui/UserManagement.html com.sap.sse.security.ui.UserManagement"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sse.security.ui"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xms1024m -Xmx3072m"/>
|
||||
</launchConfiguration>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
|
||||
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
|
||||
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
|
||||
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="true"/>
|
||||
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9875"/>
|
||||
<booleanAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT_AUTO" value="true"/>
|
||||
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
|
||||
<listEntry value="com.sap.sse.security.ui.UserManagement"/>
|
||||
</listAttribute>
|
||||
<booleanAttribute key="com.google.gwt.eclipse.core.SUPERDEVMODE_ENABLED" value="true"/>
|
||||
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/security/ui/Login.html"/>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/com.sap.sse.security.ui"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="4"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry containerPath="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" javaProject="com.sap.sse.security.ui" path="1" type="4"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.security.ui/src/main/resources" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.security.ui/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.googlecode.java-diff-utils/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/org.json.simple/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.operationaltransformation/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.replication/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/org.moxieapps.gwt.highcharts/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.datamining.shared/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-user.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath"> <memento exportedEntriesOnly="false" project="com.sap.sse.security.ui"/> </runtimeClasspathEntry> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.security/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt.adminconsole/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.security.common/src" path="3" type="2"/> "/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-superDevMode -incremental -war "${project_loc:com.sap.sse.security.ui}" -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -logLevel INFO -codeServerPort auto -startupUrl /security/ui/Login.html com.sap.sse.security.ui.Login -startupUrl /security/ui/EditProfile.html com.sap.sse.security.ui.EditProfile -startupUrl /security/ui/EmailValidation.html com.sap.sse.security.ui.EmailValidation -startupUrl /security/ui/UserManagement.html com.sap.sse.security.ui.UserManagement"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sse.security.ui"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xms1024m -Xmx3072m"/>
|
||||
</launchConfiguration>
|
||||
@@ -15,10 +15,12 @@ import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
import com.sap.sse.concurrent.LockUtil;
|
||||
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
|
||||
import com.sap.sse.util.SmartFutureCache.UpdateInterval;
|
||||
@@ -80,7 +82,7 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
||||
* {@link #ongoingManeuverCacheRecalculations}. This will let {@link Future#cancel(boolean)} return <code>false</code>
|
||||
* should it be called on that Future.
|
||||
*/
|
||||
private final Map<K, FutureTaskWithCancelBlocking> ongoingRecalculations;
|
||||
private final ConcurrentHashMap<K, FutureTaskWithCancelBlocking> ongoingRecalculations;
|
||||
|
||||
private final Map<K, V> cache;
|
||||
|
||||
@@ -113,8 +115,8 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
||||
*/
|
||||
private boolean suspended;
|
||||
|
||||
private final Map<K, U> triggeredWhileSuspended;
|
||||
|
||||
private final Map<K, Pair<U, Set<SettableFuture<Future<V>>>>> triggeredAndNotYetScheduled;
|
||||
|
||||
private int smartFutureCacheTaskReuseCounter;
|
||||
|
||||
/**
|
||||
@@ -212,8 +214,6 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
||||
*
|
||||
*/
|
||||
private class FutureTaskWithCancelBlocking extends FutureTask<V> implements Callable<V> {
|
||||
private boolean dontCancel;
|
||||
|
||||
private boolean runningAndReadUpdateInterval;
|
||||
|
||||
private final K key;
|
||||
@@ -327,7 +327,23 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
||||
return result;
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(lock);
|
||||
ongoingRecalculations.remove(key);
|
||||
synchronized (ongoingRecalculations) {
|
||||
boolean newTaskScheduled = false;
|
||||
Pair<U, Set<SettableFuture<Future<V>>>> queued = triggeredAndNotYetScheduled.get(key);
|
||||
if (!suspended || (queued != null && !queued.getB().isEmpty())) {
|
||||
if (queued != null) {
|
||||
triggeredAndNotYetScheduled.remove(key);
|
||||
Future<V> future = schedule(key, queued.getA(), /* callerWaitsSynchronouslyForResult */ false);
|
||||
newTaskScheduled = true;
|
||||
for (SettableFuture<Future<V>> futureToSet : queued.getB()) {
|
||||
futureToSet.set(future);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!newTaskScheduled) {
|
||||
ongoingRecalculations.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
synchronized (this) {
|
||||
@@ -353,23 +369,6 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void dontCancel() {
|
||||
dontCancel = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean cancel(boolean mayInterruptIfRunning) {
|
||||
if (!dontCancel) {
|
||||
return super.cancel(mayInterruptIfRunning);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCallerWaitsSynchronouslyForResult() {
|
||||
return callerWaitsSynchronouslyForResult;
|
||||
}
|
||||
|
||||
public U getUpdateInterval() {
|
||||
return updateInterval;
|
||||
}
|
||||
@@ -381,7 +380,7 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
||||
this.cacheUpdateComputer = cacheUpdateComputer;
|
||||
this.locksForKeys = new ConcurrentHashMap<K, NamedReentrantReadWriteLock>();
|
||||
this.nameForLocks = nameForLocks;
|
||||
this.triggeredWhileSuspended = new HashMap<K, U>();
|
||||
this.triggeredAndNotYetScheduled = new HashMap<>();
|
||||
}
|
||||
|
||||
private NamedReentrantReadWriteLock getOrCreateLockForKey(K key) {
|
||||
@@ -406,17 +405,30 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
||||
synchronized (ongoingRecalculations) {
|
||||
suspended = false;
|
||||
logger.finest("resuming cache "+nameForLocks);
|
||||
for (Iterator<Map.Entry<K, U>> i=triggeredWhileSuspended.entrySet().iterator(); i.hasNext(); ) {
|
||||
Entry<K, U> e = i.next();
|
||||
for (Iterator<Entry<K, Pair<U, Set<SettableFuture<Future<V>>>>>> i=triggeredAndNotYetScheduled.entrySet().iterator(); i.hasNext(); ) {
|
||||
Entry<K, Pair<U, Set<SettableFuture<Future<V>>>>> e = i.next();
|
||||
if (logger.isLoggable(Level.FINEST)) {
|
||||
logger.finest("while resuming "+nameForLocks+", triggering update for key "+e.getKey()+" with update interval "+e.getValue());
|
||||
}
|
||||
triggerUpdate(e.getKey(), e.getValue());
|
||||
triggerUpdate(e.getKey(), e.getValue().getA());
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void queue(final K key, final U updateInterval, SettableFuture<Future<V>> setWhenDone) {
|
||||
Pair<U, Set<SettableFuture<Future<V>>>> oldUpdateInterval = triggeredAndNotYetScheduled.get(key);
|
||||
U joinedUpdateInterval = joinUpdateIntervals(updateInterval, oldUpdateInterval==null?null:oldUpdateInterval.getA());
|
||||
Set<SettableFuture<Future<V>>> newSetWhenDone = new HashSet<>();
|
||||
if (oldUpdateInterval != null) {
|
||||
newSetWhenDone.addAll(oldUpdateInterval.getB());
|
||||
}
|
||||
if (setWhenDone != null) {
|
||||
newSetWhenDone.add(setWhenDone);
|
||||
}
|
||||
triggeredAndNotYetScheduled.put(key, new Pair<>(joinedUpdateInterval, newSetWhenDone));
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a cache update for <code>key</code> for the <code>updateInterval</code> specified. If a re-calculation
|
||||
* for this key is already scheduled, this method will try to update its update interval by
|
||||
@@ -432,73 +444,188 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
||||
// establish and maintain the following invariant: after lock on ongoingRecalculations is released,
|
||||
// no Future contained in it is in cancelled state
|
||||
synchronized (ongoingRecalculations) {
|
||||
final U oldUpdateInterval;
|
||||
final U joinedUpdateInterval;
|
||||
FutureTaskWithCancelBlocking oldFuture = null;
|
||||
if (suspended) {
|
||||
oldUpdateInterval = triggeredWhileSuspended.get(key);
|
||||
queue(key, updateInterval, /* future to set when done */ null);
|
||||
} else {
|
||||
oldFuture = ongoingRecalculations.get(key);
|
||||
oldUpdateInterval = oldFuture == null ? null : oldFuture.getUpdateInterval();
|
||||
}
|
||||
if (oldUpdateInterval == null) {
|
||||
joinedUpdateInterval = updateInterval;
|
||||
} else {
|
||||
if (updateInterval == null) {
|
||||
joinedUpdateInterval = oldUpdateInterval;
|
||||
} else {
|
||||
joinedUpdateInterval = updateInterval.join(oldUpdateInterval);
|
||||
}
|
||||
}
|
||||
if (suspended) {
|
||||
// Turn this logging on in case you need to debug the SmartFutureCache. Otherwise, log level detection is too expensive here.
|
||||
// logger.finest("triggerUpdate on cache "+nameForLocks+" while suspended for key "+key);
|
||||
triggeredWhileSuspended.put(key, joinedUpdateInterval);
|
||||
} else {
|
||||
boolean reuseExistingFuture = false;
|
||||
if (oldFuture != null) {
|
||||
if (!oldFuture.isCallerWaitsSynchronouslyForResult()) {
|
||||
reuseExistingFuture = oldFuture.tryToUpdateUpdateInterval(joinedUpdateInterval);
|
||||
}
|
||||
if (!reuseExistingFuture) {
|
||||
// Turn this logging on in case you need to debug the SmartFutureCache. Otherwise, log level detection is too expensive here.
|
||||
// logger.finest("cancelling existing future task on cache "+nameForLocks+" for key "+key);
|
||||
oldFuture.cancel(/* mayInterruptIfRunning */ false);
|
||||
} else {
|
||||
// Turn this logging on in case you need to debug the SmartFutureCache. Otherwise, log level detection is too expensive here.
|
||||
// logger.finest("re-using existing future task on cache "+nameForLocks+" for key "+key);
|
||||
smartFutureCacheTaskReuseCounter++;
|
||||
}
|
||||
}
|
||||
if (!reuseExistingFuture) {
|
||||
// the following call adds the resulting new Future to ongoingRecalculations
|
||||
createAndExecuteRecalculation(key, joinedUpdateInterval, /* callerWaitsSynchronouslyForResult */ false);
|
||||
}
|
||||
scheduleRecalculationIfNotScheduledOrRunningOtherwiseUpdateScheduledTaskOrQueueIfAlreadyRunning(key, updateInterval,
|
||||
/* callerWaitsSynchronouslyForResult */ false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private U joinUpdateIntervals(U updateInterval, final U oldUpdateInterval) {
|
||||
final U joinedUpdateInterval;
|
||||
if (oldUpdateInterval == null) {
|
||||
joinedUpdateInterval = updateInterval;
|
||||
} else {
|
||||
if (updateInterval == null) {
|
||||
joinedUpdateInterval = oldUpdateInterval;
|
||||
} else {
|
||||
joinedUpdateInterval = updateInterval.join(oldUpdateInterval);
|
||||
}
|
||||
}
|
||||
return joinedUpdateInterval;
|
||||
}
|
||||
|
||||
private static class SettableFuture<T> implements Future<T> {
|
||||
private T value;
|
||||
private boolean isSet;
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return isSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() throws InterruptedException, ExecutionException {
|
||||
synchronized (this) {
|
||||
while (!isSet) {
|
||||
wait();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
|
||||
synchronized (this) {
|
||||
while (!isSet) {
|
||||
wait(TimeUnit.MILLISECONDS.convert(timeout, unit));
|
||||
if (!isSet) {
|
||||
throw new TimeoutException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public void set(T value) {
|
||||
synchronized (this) {
|
||||
this.value = value;
|
||||
isSet = true;
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class TransitiveFuture<T> implements Future<T> {
|
||||
private final SettableFuture<Future<T>> future;
|
||||
|
||||
protected TransitiveFuture(SettableFuture<Future<T>> future) {
|
||||
super();
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
try {
|
||||
return future.get().cancel(mayInterruptIfRunning);
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
try {
|
||||
return future.get().isCancelled();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
try {
|
||||
return future.get().isDone();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() throws InterruptedException, ExecutionException {
|
||||
return future.get().get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return future.get(timeout, unit).get(timeout, unit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Either calls {@link #schedule(Object, UpdateInterval, boolean)} to create and execute the
|
||||
* future or, if there is a currently executing future with this key, "remember" the call in the map
|
||||
* {@link #triggeredWhileExecutingForSameKey}. This map is then looked into in the finally block of
|
||||
* {@link FutureTaskWithCancelBlocking#call()}.
|
||||
*
|
||||
* @param callerWaitsSynchronouslyForResult if <code>true</code>, if a future task needs to be created,
|
||||
* it will already inherit the calling thread's locks, and the method will always return a future task.
|
||||
*/
|
||||
private Future<V> scheduleRecalculationIfNotScheduledOrRunningOtherwiseUpdateScheduledTaskOrQueueIfAlreadyRunning(final K key,
|
||||
final U updateInterval, final boolean callerWaitsSynchronouslyForResult) {
|
||||
final Future<V> result;
|
||||
synchronized (ongoingRecalculations) {
|
||||
if (ongoingRecalculations.containsKey(key)) {
|
||||
FutureTaskWithCancelBlocking scheduledOrRunning = ongoingRecalculations.get(key);
|
||||
// a future is already scheduled for the key; try to
|
||||
boolean reuseExistingFuture = scheduledOrRunning.tryToUpdateUpdateInterval(updateInterval);
|
||||
if (reuseExistingFuture) {
|
||||
result = scheduledOrRunning;
|
||||
// Turn this logging on in case you need to debug the SmartFutureCache. Otherwise, log level detection is too expensive here.
|
||||
// logger.finest("re-using existing future task on cache "+nameForLocks+" for key "+key);
|
||||
smartFutureCacheTaskReuseCounter++;
|
||||
} else {
|
||||
final SettableFuture<Future<V>> nestedFuture;
|
||||
if (callerWaitsSynchronouslyForResult) {
|
||||
nestedFuture = new SettableFuture<Future<V>>();
|
||||
result = new TransitiveFuture<>(nestedFuture);
|
||||
} else {
|
||||
nestedFuture = null;
|
||||
result = null;
|
||||
}
|
||||
queue(key, updateInterval, nestedFuture);
|
||||
}
|
||||
} else {
|
||||
result = schedule(key, updateInterval, callerWaitsSynchronouslyForResult);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FutureTask} for the (re-)calculation of the cache entry for <code>key</code> across update
|
||||
* interval <code>joinedUpdateInterval</code>, enters it into {@link #ongoingRecalculations} and schedules its
|
||||
* execution with {@link #recalculator}. The method synchronizes on {@link #ongoingRecalculations}.
|
||||
*
|
||||
* !!! Callers need to hold monitor of {@link #ongoingRecalculations}
|
||||
*
|
||||
* @param callerWaitsSynchronouslyForResult
|
||||
* if <code>true</code>, this allows the future to assume the caller's locks. See also
|
||||
* {@link LockUtil#propagateLockSetFrom(Thread)}. This can be helpful to avoid read-read deadlocks
|
||||
* in conjunction with fair {@link ReentrantReadWriteLock}s.
|
||||
*/
|
||||
private void createAndExecuteRecalculation(final K key, final U joinedUpdateInterval,
|
||||
final boolean callerWaitsSynchronouslyForResult) {
|
||||
private Future<V> schedule(final K key, final U joinedUpdateInterval, final boolean callerWaitsSynchronouslyForResult) {
|
||||
final Thread callerThread = Thread.currentThread();
|
||||
synchronized (ongoingRecalculations) {
|
||||
final FutureTaskWithCancelBlocking future = new FutureTaskWithCancelBlocking(key,
|
||||
joinedUpdateInterval, callerWaitsSynchronouslyForResult, callerThread);
|
||||
// Turn this logging on in case you need to debug the SmartFutureCache. Otherwise, log level detection is too expensive here.
|
||||
// logger.finest("creating future task on cache "+nameForLocks+" for key "+key);
|
||||
ongoingRecalculations.put(key, future);
|
||||
recalculator.execute(future);
|
||||
}
|
||||
final FutureTaskWithCancelBlocking future = new FutureTaskWithCancelBlocking(key, joinedUpdateInterval,
|
||||
callerWaitsSynchronouslyForResult, callerThread);
|
||||
// Turn this logging on in case you need to debug the SmartFutureCache. Otherwise, log level detection
|
||||
// is too expensive here.
|
||||
// logger.finest("creating future task on cache "+nameForLocks+" for key "+key);
|
||||
ongoingRecalculations.put(key, future);
|
||||
recalculator.execute(future);
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -508,50 +635,42 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
||||
* ongoing, the result of that ongoing re-calculation is returned.
|
||||
*/
|
||||
public V get(final K key, boolean waitForLatest) {
|
||||
V value = null;
|
||||
final V value;
|
||||
final Future<V> future;
|
||||
if (waitForLatest) {
|
||||
FutureTaskWithCancelBlocking future;
|
||||
synchronized (ongoingRecalculations) {
|
||||
if (suspended) { // waitForLatest is true, see "if" above
|
||||
final boolean wasTriggeredWhileSuspended = triggeredWhileSuspended.containsKey(key); // update interval may have deliberately been null
|
||||
if (wasTriggeredWhileSuspended) {
|
||||
// If suspended and the caller wants to get the latest results, and while suspended recalculations were
|
||||
// triggered, execute them now.
|
||||
// This will enter the new future into ongoingRecalculations, and therefore this method
|
||||
// will wait for its completion below.
|
||||
// It is OK to assert that this method will synchronously wait for the results before
|
||||
// releasing any locks held by the current thread because
|
||||
// createAndExecuteRecalculation adds key to ongoingRecalculations, and
|
||||
// further down this method, the future will be fetched from ongoingRecalculations
|
||||
// with that same key, and a get will be performed on that future.
|
||||
createAndExecuteRecalculation(key, triggeredWhileSuspended.remove(key),
|
||||
/* callerWaitsSynchronouslyForResult */ true);
|
||||
}
|
||||
}
|
||||
// as long as we hold the lock on ongoingManeuverCacheRecalculations, the Futures contained in it are not cancelled
|
||||
future = ongoingRecalculations.get(key);
|
||||
if (future != null) {
|
||||
future.dontCancel();
|
||||
final Pair<U, Set<SettableFuture<Future<V>>>> triggeredSinceLastCacheUpdate = triggeredAndNotYetScheduled.remove(key);
|
||||
if (triggeredSinceLastCacheUpdate != null) {
|
||||
future = scheduleRecalculationIfNotScheduledOrRunningOtherwiseUpdateScheduledTaskOrQueueIfAlreadyRunning(key, triggeredSinceLastCacheUpdate.getA(),
|
||||
/* callerWaitsSynchronouslyForResult: if a new task is scheduled, we'll wait for it by calling future.get() below */ true);
|
||||
} else {
|
||||
value = cache.get(key);
|
||||
future = ongoingRecalculations.get(key);
|
||||
}
|
||||
}
|
||||
if (future != null) {
|
||||
try {
|
||||
value = future.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
logger.log(Level.SEVERE, "get", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} // else no calculation currently going on; value has been fetched from latest cache entry
|
||||
} else {
|
||||
final NamedReentrantReadWriteLock lock = getOrCreateLockForKey(key);
|
||||
LockUtil.lockForRead(lock);
|
||||
try {
|
||||
value = cache.get(key);
|
||||
} finally {
|
||||
LockUtil.unlockAfterRead(lock);
|
||||
if (future != null) {
|
||||
value = future.get();
|
||||
} else {
|
||||
value = readCache(key);
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
logger.log(Level.SEVERE, "get", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
value = readCache(key);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private V readCache(final K key) {
|
||||
final V value;
|
||||
final NamedReentrantReadWriteLock lock = getOrCreateLockForKey(key);
|
||||
LockUtil.lockForRead(lock);
|
||||
try {
|
||||
value = cache.get(key);
|
||||
} finally {
|
||||
LockUtil.unlockAfterRead(lock);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user