Merge pull request #547 from DataDog/mar-kolya/weak-concurrent-map-thread-cleanup
Mar kolya/weak concurrent map thread cleanup
This commit is contained in:
commit
14e85941c0
|
@ -25,7 +25,7 @@ jobs:
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Build Project
|
name: Build Project
|
||||||
command: GRADLE_OPTS="-Dorg.gradle.jvmargs=-Xmx1G -Xms64M" ./gradlew clean :dd-java-agent:shadowJar compileTestGroovy compileTestScala compileTestJava --build-cache --parallel --stacktrace --no-daemon --max-workers=4
|
command: GRADLE_OPTS="-Dorg.gradle.jvmargs=-Xmx1G -Xms64M" ./gradlew clean :dd-java-agent:shadowJar compileTestGroovy compileTestScala compileTestJava --build-cache --parallel --stacktrace --no-daemon --max-workers=8
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Collect Libs
|
name: Collect Libs
|
||||||
|
@ -66,7 +66,7 @@ jobs:
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Run Tests
|
name: Run Tests
|
||||||
command: GRADLE_OPTS="-Dorg.gradle.jvmargs=-Xmx2G -Xms512M" ./gradlew $TEST_TASK --build-cache --parallel --stacktrace --no-daemon --max-workers=3
|
command: GRADLE_OPTS="-Dorg.gradle.jvmargs=-Xmx2G -Xms512M" ./gradlew $TEST_TASK --build-cache --parallel --stacktrace --no-daemon --max-workers=6
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Collect Reports
|
name: Collect Reports
|
||||||
|
@ -130,7 +130,7 @@ jobs:
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Run Trace Agent Tests
|
name: Run Trace Agent Tests
|
||||||
command: ./gradlew traceAgentTest --build-cache --parallel --stacktrace --no-daemon --max-workers=6
|
command: ./gradlew traceAgentTest --build-cache --parallel --stacktrace --no-daemon --max-workers=8
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Collect Reports
|
name: Collect Reports
|
||||||
|
@ -162,7 +162,7 @@ jobs:
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Build Project
|
name: Build Project
|
||||||
command: GRADLE_OPTS="-Dorg.gradle.jvmargs=-Xmx1G -Xms64M" ./gradlew check -PskipTests --build-cache --parallel --stacktrace --no-daemon --max-workers=4
|
command: GRADLE_OPTS="-Dorg.gradle.jvmargs=-Xmx1G -Xms64M" ./gradlew check -PskipTests --build-cache --parallel --stacktrace --no-daemon --max-workers=8
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Collect Reports
|
name: Collect Reports
|
||||||
|
@ -183,13 +183,34 @@ jobs:
|
||||||
- dd-trace-java-version-scan-{{ checksum "dd-trace-java.gradle" }}
|
- dd-trace-java-version-scan-{{ checksum "dd-trace-java.gradle" }}
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Verify Version Scan and Muzzle
|
name: Verify Version Scan
|
||||||
command: GRADLE_OPTS="-Dorg.gradle.jvmargs=-Xmx4G -Xms64M" ./gradlew verifyVersionScan muzzle --parallel --stacktrace --no-daemon --max-workers=2
|
command: GRADLE_OPTS="-Dorg.gradle.jvmargs=-Xmx4G -Xms64M" ./gradlew verifyVersionScan --parallel --stacktrace --no-daemon --max-workers=8
|
||||||
|
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: dd-trace-java-version-scan-{{ checksum "dd-trace-java.gradle" }}
|
key: dd-trace-java-version-scan-{{ checksum "dd-trace-java.gradle" }}
|
||||||
paths: ~/.gradle
|
paths: ~/.gradle
|
||||||
|
|
||||||
|
muzzle:
|
||||||
|
<<: *defaults
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
|
||||||
|
- restore_cache:
|
||||||
|
# Reset the cache approx every release
|
||||||
|
keys:
|
||||||
|
- dd-trace-java-muzzle-{{ checksum "dd-trace-java.gradle" }}
|
||||||
|
|
||||||
|
- run:
|
||||||
|
name: Verify Muzzle
|
||||||
|
# Note: we do not have `--max-workers` here to have number of workers (threads) equal to number of CPUs (32 currently).
|
||||||
|
# This should speed things up slightly because muzzle may do a lot of IO bound work: reading off disk and downloading
|
||||||
|
# dependencies.
|
||||||
|
command: GRADLE_OPTS="-Dorg.gradle.jvmargs=-Xmx12G -Xms64M" ./gradlew muzzle --parallel --stacktrace --no-daemon
|
||||||
|
|
||||||
|
- save_cache:
|
||||||
|
key: dd-trace-java-muzzle-{{ checksum "dd-trace-java.gradle" }}
|
||||||
|
paths: ~/.gradle
|
||||||
|
|
||||||
publish: &publish
|
publish: &publish
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
steps:
|
steps:
|
||||||
|
@ -276,6 +297,13 @@ workflows:
|
||||||
branches:
|
branches:
|
||||||
ignore: master
|
ignore: master
|
||||||
|
|
||||||
|
- muzzle:
|
||||||
|
requires:
|
||||||
|
- build
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
ignore: master
|
||||||
|
|
||||||
- publish_master:
|
- publish_master:
|
||||||
requires:
|
requires:
|
||||||
- test_7
|
- test_7
|
||||||
|
|
|
@ -12,6 +12,7 @@ import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
class WeakMapSuppliers {
|
class WeakMapSuppliers {
|
||||||
// Comparison with using WeakConcurrentMap vs Guava's implementation:
|
// Comparison with using WeakConcurrentMap vs Guava's implementation:
|
||||||
|
@ -57,48 +58,28 @@ class WeakMapSuppliers {
|
||||||
private final Queue<WeakReference<WeakConcurrentMap>> suppliedMaps =
|
private final Queue<WeakReference<WeakConcurrentMap>> suppliedMaps =
|
||||||
new ConcurrentLinkedQueue<>();
|
new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
private final Runnable runnable =
|
private final AtomicBoolean finalized = new AtomicBoolean(false);
|
||||||
new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
for (final Iterator<WeakReference<WeakConcurrentMap>> iterator =
|
|
||||||
suppliedMaps.iterator();
|
|
||||||
iterator.hasNext(); ) {
|
|
||||||
final WeakConcurrentMap map = iterator.next().get();
|
|
||||||
if (map == null) {
|
|
||||||
iterator.remove();
|
|
||||||
} else {
|
|
||||||
map.expungeStaleEntries();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
WeakConcurrent() {
|
WeakConcurrent() {
|
||||||
cleanerExecutorService = Executors.newScheduledThreadPool(1, THREAD_FACTORY);
|
cleanerExecutorService = Executors.newScheduledThreadPool(1, THREAD_FACTORY);
|
||||||
cleanerExecutorService.scheduleAtFixedRate(
|
cleanerExecutorService.scheduleAtFixedRate(
|
||||||
runnable, CLEAN_FREQUENCY_SECONDS, CLEAN_FREQUENCY_SECONDS, TimeUnit.SECONDS);
|
new CleanupRunnable(cleanerExecutorService, suppliedMaps, finalized),
|
||||||
|
CLEAN_FREQUENCY_SECONDS,
|
||||||
|
CLEAN_FREQUENCY_SECONDS,
|
||||||
|
TimeUnit.SECONDS);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Runtime.getRuntime()
|
Runtime.getRuntime().addShutdownHook(new ShutdownCallback(cleanerExecutorService));
|
||||||
.addShutdownHook(
|
|
||||||
new Thread() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
cleanerExecutorService.shutdownNow();
|
|
||||||
cleanerExecutorService.awaitTermination(
|
|
||||||
SHUTDOWN_WAIT_SECONDS, TimeUnit.SECONDS);
|
|
||||||
} catch (final InterruptedException e) {
|
|
||||||
// Don't bother waiting then...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (final IllegalStateException ex) {
|
} catch (final IllegalStateException ex) {
|
||||||
// The JVM is already shutting down.
|
// The JVM is already shutting down.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finalize() {
|
||||||
|
finalized.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <K, V> WeakMap<K, V> get() {
|
public <K, V> WeakMap<K, V> get() {
|
||||||
final WeakConcurrentMap<K, V> map = new WeakConcurrentMap<>(false);
|
final WeakConcurrentMap<K, V> map = new WeakConcurrentMap<>(false);
|
||||||
|
@ -106,6 +87,57 @@ class WeakMapSuppliers {
|
||||||
return new Adapter<>(map);
|
return new Adapter<>(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class CleanupRunnable implements Runnable {
|
||||||
|
|
||||||
|
private final ScheduledExecutorService executorService;
|
||||||
|
private final Queue<WeakReference<WeakConcurrentMap>> suppliedMaps;
|
||||||
|
private final AtomicBoolean finalized;
|
||||||
|
|
||||||
|
public CleanupRunnable(
|
||||||
|
final ScheduledExecutorService executorService,
|
||||||
|
final Queue<WeakReference<WeakConcurrentMap>> suppliedMaps,
|
||||||
|
final AtomicBoolean finalized) {
|
||||||
|
this.executorService = executorService;
|
||||||
|
this.suppliedMaps = suppliedMaps;
|
||||||
|
this.finalized = finalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
for (final Iterator<WeakReference<WeakConcurrentMap>> iterator = suppliedMaps.iterator();
|
||||||
|
iterator.hasNext(); ) {
|
||||||
|
final WeakConcurrentMap map = iterator.next().get();
|
||||||
|
if (map == null) {
|
||||||
|
iterator.remove();
|
||||||
|
} else {
|
||||||
|
map.expungeStaleEntries();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (finalized.get() && suppliedMaps.isEmpty()) {
|
||||||
|
executorService.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ShutdownCallback extends Thread {
|
||||||
|
|
||||||
|
private final ScheduledExecutorService executorService;
|
||||||
|
|
||||||
|
public ShutdownCallback(final ScheduledExecutorService executorService) {
|
||||||
|
this.executorService = executorService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
executorService.shutdownNow();
|
||||||
|
executorService.awaitTermination(SHUTDOWN_WAIT_SECONDS, TimeUnit.SECONDS);
|
||||||
|
} catch (final InterruptedException e) {
|
||||||
|
// Don't bother waiting then...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class Adapter<K, V> implements WeakMap<K, V> {
|
private static class Adapter<K, V> implements WeakMap<K, V> {
|
||||||
private final WeakConcurrentMap<K, V> map;
|
private final WeakConcurrentMap<K, V> map;
|
||||||
|
|
||||||
|
@ -150,7 +182,7 @@ class WeakMapSuppliers {
|
||||||
return new WeakMap.MapAdapter<>(new MapMaker().weakKeys().<K, V>makeMap());
|
return new WeakMap.MapAdapter<>(new MapMaker().weakKeys().<K, V>makeMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
public <K, V> WeakMap<K, V> get(int concurrencyLevel) {
|
public <K, V> WeakMap<K, V> get(final int concurrencyLevel) {
|
||||||
return new WeakMap.MapAdapter<>(
|
return new WeakMap.MapAdapter<>(
|
||||||
new MapMaker().concurrencyLevel(concurrencyLevel).weakKeys().<K, V>makeMap());
|
new MapMaker().concurrencyLevel(concurrencyLevel).weakKeys().<K, V>makeMap());
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,14 +38,35 @@ class WeakConcurrentSupplierTest extends Specification {
|
||||||
"Guava" | guavaSupplier
|
"Guava" | guavaSupplier
|
||||||
}
|
}
|
||||||
|
|
||||||
def "Unreferenced map get cleaned up on #name"() {
|
def "Unreferenced supplier gets cleaned up on #name"() {
|
||||||
|
setup:
|
||||||
|
// Note: we use 'double supplier' here because Groovy keeps reference to test data preventing it from being GCed
|
||||||
|
def supplier = supplierSupplier()
|
||||||
|
def ref = new WeakReference(supplier)
|
||||||
|
|
||||||
|
when:
|
||||||
|
def supplierRef = new WeakReference(supplier)
|
||||||
|
supplier = null
|
||||||
|
TestUtils.awaitGC(supplierRef)
|
||||||
|
|
||||||
|
then:
|
||||||
|
ref.get() == null
|
||||||
|
|
||||||
|
where:
|
||||||
|
name | supplierSupplier
|
||||||
|
"WeakConcurrent" | { -> new WeakMapSuppliers.WeakConcurrent() }
|
||||||
|
"WeakInline" | { -> new WeakMapSuppliers.WeakConcurrent.Inline() }
|
||||||
|
"Guava" | { -> new WeakMapSuppliers.Guava() }
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Unreferenced map gets cleaned up on #name"() {
|
||||||
setup:
|
setup:
|
||||||
WeakMap.Provider.provider.set(supplier)
|
WeakMap.Provider.provider.set(supplier)
|
||||||
def map = WeakMap.Provider.newWeakMap()
|
def map = WeakMap.Provider.newWeakMap()
|
||||||
def ref = new WeakReference(map)
|
def ref = new WeakReference(map)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def mapRef = new WeakReference<>(map)
|
def mapRef = new WeakReference(map)
|
||||||
map = null
|
map = null
|
||||||
TestUtils.awaitGC(mapRef)
|
TestUtils.awaitGC(mapRef)
|
||||||
|
|
||||||
|
@ -69,7 +90,7 @@ class WeakConcurrentSupplierTest extends Specification {
|
||||||
map.size() == 1
|
map.size() == 1
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def keyRef = new WeakReference<>(key)
|
def keyRef = new WeakReference(key)
|
||||||
key = null
|
key = null
|
||||||
TestUtils.awaitGC(keyRef)
|
TestUtils.awaitGC(keyRef)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'io.franzbecker.gradle-lombok' version '1.13'
|
id 'io.franzbecker.gradle-lombok' version '1.13'
|
||||||
id 'com.jfrog.artifactory' version '4.7.5'
|
id 'com.jfrog.artifactory' version '4.7.5'
|
||||||
|
|
|
@ -145,6 +145,7 @@ if (project.parent && project.parent.hasProperty("javaExecutableVersionCache"))
|
||||||
} else {
|
} else {
|
||||||
project.ext.javaExecutableVersionCache = [:]
|
project.ext.javaExecutableVersionCache = [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
def getJavaExecutableVersion(String path) {
|
def getJavaExecutableVersion(String path) {
|
||||||
def cache = project.ext.javaExecutableVersionCache
|
def cache = project.ext.javaExecutableVersionCache
|
||||||
|
|
||||||
|
@ -177,7 +178,6 @@ def isJavaVersionAllowed(JavaVersion version) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// JVM names we would like to run complete test suite on
|
// JVM names we would like to run complete test suite on
|
||||||
// Note: complete test suite is always run on JVM used for compilation
|
// Note: complete test suite is always run on JVM used for compilation
|
||||||
// Note2: apparently there is no way to have a 'global' variable, so instead we have per project
|
// Note2: apparently there is no way to have a 'global' variable, so instead we have per project
|
||||||
|
|
Loading…
Reference in New Issue