Merge branch 'master' into ubilabs--rcapp--develop

This commit is contained in:
Peter Siegmund committed 2015-03-17 11:10:06 +01:00
commit 03924c1df2
7 files changed
+413 -168

No files matched your search

@@ -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);
}
}
@@ -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="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; javaProject=&quot;com.sap.sse.security.ui&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.ui/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.googlecode.java-diff-utils/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.json.simple/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.operationaltransformation/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.replication/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-user.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sse.security.ui&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
</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 &quot;${project_loc:com.sap.sse.security.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -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="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; javaProject=&quot;com.sap.sse.security.ui&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.ui/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.googlecode.java-diff-utils/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.json.simple/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.operationaltransformation/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.replication/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-user.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sse.security.ui&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
</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 &quot;${project_loc:com.sap.sse.security.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -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;
}