From 75c77691923935db9c23718619e92d2fc03facb5 Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Tue, 4 Feb 2020 12:30:25 -0800 Subject: [PATCH] Reduce tracer thread count by combining scheduled executors into a single executor. Pulls out utility classes for reuse by other projects. This also meant the dependency had to be bundled with dd-trace-ot since it isn't published as a separate dependency. --- .../agent-bootstrap/agent-bootstrap.gradle | 4 ++ .../agent-tooling/agent-tooling.gradle | 1 - .../datadog/trace/agent/tooling/Cleaner.java | 70 ++----------------- .../trace/agent/tooling/CleanerTest.groovy | 23 ++---- dd-java-agent/dd-java-agent.gradle | 2 +- dd-java-agent/testing/testing.gradle | 1 - dd-trace-ot/dd-trace-ot.gradle | 21 +++++- .../java/datadog/opentracing/DDTracer.java | 1 + .../datadog/opentracing/PendingTrace.java | 27 ++----- .../writer/ddagent/AbstractDisruptor.java | 4 +- .../writer/ddagent/BatchWritingDisruptor.java | 18 ++--- .../common/writer/ddagent/DDAgentApi.java | 5 ++ .../ddagent/TraceProcessingDisruptor.java | 9 +-- gradle/java.gradle | 8 ++- settings.gradle | 2 +- .../java/datadog/trace/util/gc/GCUtils.java | 0 .../common/exec}/DaemonThreadFactory.java | 8 ++- .../datadog/common/exec/SharedExecutors.java | 56 +++++++++++++++ .../thread-utils.gradle} | 0 19 files changed, 130 insertions(+), 130 deletions(-) rename utils/{gc-utils => test-utils}/src/main/java/datadog/trace/util/gc/GCUtils.java (100%) rename {dd-trace-ot/src/main/java/datadog/trace/common/util => utils/thread-utils/src/main/java/datadog/common/exec}/DaemonThreadFactory.java (63%) create mode 100644 utils/thread-utils/src/main/java/datadog/common/exec/SharedExecutors.java rename utils/{gc-utils/gc-utils.gradle => thread-utils/thread-utils.gradle} (100%) diff --git a/dd-java-agent/agent-bootstrap/agent-bootstrap.gradle b/dd-java-agent/agent-bootstrap/agent-bootstrap.gradle index eb60f16435..84d872a4d7 100644 --- a/dd-java-agent/agent-bootstrap/agent-bootstrap.gradle +++ b/dd-java-agent/agent-bootstrap/agent-bootstrap.gradle @@ -5,6 +5,10 @@ plugins { apply from: "${rootDir}/gradle/java.gradle" +// FIXME: Improve test coverage. +minimumBranchCoverage = 0.0 +minimumInstructionCoverage = 0.0 + dependencies { compile project(':dd-trace-api') compile deps.opentracing diff --git a/dd-java-agent/agent-tooling/agent-tooling.gradle b/dd-java-agent/agent-tooling/agent-tooling.gradle index bef4b949c7..a09b096a69 100644 --- a/dd-java-agent/agent-tooling/agent-tooling.gradle +++ b/dd-java-agent/agent-tooling/agent-tooling.gradle @@ -23,7 +23,6 @@ dependencies { testCompile deps.opentracing testCompile project(':dd-java-agent:testing') - testCompile project(':utils:gc-utils') instrumentationMuzzle sourceSets.main.output instrumentationMuzzle configurations.compile diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/Cleaner.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/Cleaner.java index 61529aff63..1813b87cd5 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/Cleaner.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/Cleaner.java @@ -1,70 +1,33 @@ package datadog.trace.agent.tooling; +import static datadog.common.exec.SharedExecutors.isTaskSchedulerShutdown; +import static datadog.common.exec.SharedExecutors.scheduleTaskAtFixedRate; + import java.lang.ref.WeakReference; import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; @Slf4j class Cleaner { - private static final long SHUTDOWN_WAIT_SECONDS = 5; - - private static final ThreadFactory THREAD_FACTORY = - new ThreadFactory() { - @Override - public Thread newThread(final Runnable r) { - final Thread thread = new Thread(r, "dd-cleaner"); - thread.setDaemon(true); - thread.setPriority(Thread.MIN_PRIORITY); - thread.setContextClassLoader(null); - return thread; - } - }; - - private final ScheduledThreadPoolExecutor cleanerService; - private final Thread shutdownCallback; - - Cleaner() { - cleanerService = new ScheduledThreadPoolExecutor(1, THREAD_FACTORY); - cleanerService.setRemoveOnCancelPolicy(true); - shutdownCallback = new ShutdownCallback(cleanerService); - try { - Runtime.getRuntime().addShutdownHook(shutdownCallback); - } catch (final IllegalStateException ex) { - // The JVM is already shutting down. - } - } void scheduleCleaning( final T target, final Adapter adapter, final long frequency, final TimeUnit unit) { final CleanupRunnable command = new CleanupRunnable<>(target, adapter); - if (cleanerService.isShutdown()) { - log.warn("Cleaning scheduled but cleaner is shutdown. Target won't be cleaned {}", target); + if (isTaskSchedulerShutdown()) { + log.warn( + "Cleaning scheduled but task scheduler is shutdown. Target won't be cleaned {}", target); } else { try { // Schedule job and save future to allow job to be canceled if target is GC'd. - command.setFuture(cleanerService.scheduleAtFixedRate(command, frequency, frequency, unit)); + command.setFuture(scheduleTaskAtFixedRate(command, frequency, frequency, unit)); } catch (final RejectedExecutionException e) { log.warn("Cleaning task rejected. Target won't be cleaned {}", target); } } } - private void stop() { - cleanerService.shutdownNow(); - Runtime.getRuntime().removeShutdownHook(shutdownCallback); - } - - @Override - public void finalize() { - // Do we really want this? - stop(); - } - public interface Adapter { void clean(T target); } @@ -93,23 +56,4 @@ class Cleaner { this.future = future; } } - - private static final class ShutdownCallback extends Thread { - - private final ScheduledExecutorService executorService; - - private 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... - } - } - } } diff --git a/dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/tooling/CleanerTest.groovy b/dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/tooling/CleanerTest.groovy index 9611fd01e2..c74481c784 100644 --- a/dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/tooling/CleanerTest.groovy +++ b/dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/tooling/CleanerTest.groovy @@ -8,6 +8,7 @@ import java.lang.ref.WeakReference import java.util.concurrent.CountDownLatch import java.util.concurrent.atomic.AtomicInteger +import static datadog.common.exec.SharedExecutors.isTaskSchedulerShutdown import static java.util.concurrent.TimeUnit.MILLISECONDS class CleanerTest extends DDSpecification { @@ -15,10 +16,6 @@ class CleanerTest extends DDSpecification { @Subject def cleaner = new Cleaner() - def cleanup() { - cleaner.stop() - } - def "test scheduling"() { setup: def latch = new CountDownLatch(2) @@ -31,7 +28,7 @@ class CleanerTest extends DDSpecification { } expect: - !cleaner.cleanerService.isShutdown() + !isTaskSchedulerShutdown() when: cleaner.scheduleCleaning(target, action, 10, MILLISECONDS) @@ -52,7 +49,7 @@ class CleanerTest extends DDSpecification { } expect: - !cleaner.cleanerService.isShutdown() + !isTaskSchedulerShutdown() when: cleaner.scheduleCleaning(target.get(), action, 10, MILLISECONDS) @@ -76,7 +73,7 @@ class CleanerTest extends DDSpecification { } expect: - !cleaner.cleanerService.isShutdown() + !isTaskSchedulerShutdown() when: cleaner.scheduleCleaning(null, action, 10, MILLISECONDS) @@ -85,16 +82,4 @@ class CleanerTest extends DDSpecification { then: callCount.get() == 0 } - - def "test shutdown"() { - expect: - !cleaner.cleanerService.isShutdown() - - when: - cleaner.stop() - - then: - cleaner.cleanerService.awaitTermination(50, MILLISECONDS) - cleaner.cleanerService.isTerminated() - } } diff --git a/dd-java-agent/dd-java-agent.gradle b/dd-java-agent/dd-java-agent.gradle index a3229e9900..4fc023cb3a 100644 --- a/dd-java-agent/dd-java-agent.gradle +++ b/dd-java-agent/dd-java-agent.gradle @@ -103,7 +103,7 @@ dependencies { testCompile project(':dd-java-agent:agent-bootstrap') testCompile project(':dd-trace-api') testCompile project(':dd-trace-ot') - testCompile project(':utils:gc-utils') + testCompile project(':utils:test-utils') testCompile deps.opentracingMock testCompile deps.testLogging diff --git a/dd-java-agent/testing/testing.gradle b/dd-java-agent/testing/testing.gradle index 594f938af7..2d702c6aa4 100644 --- a/dd-java-agent/testing/testing.gradle +++ b/dd-java-agent/testing/testing.gradle @@ -31,7 +31,6 @@ dependencies { compile deps.groovy - testCompile project(':utils:gc-utils') testCompile project(':utils:test-utils') testCompile project(':dd-java-agent:instrumentation:trace-annotation') diff --git a/dd-trace-ot/dd-trace-ot.gradle b/dd-trace-ot/dd-trace-ot.gradle index b4934dfb53..3d5772654e 100644 --- a/dd-trace-ot/dd-trace-ot.gradle +++ b/dd-trace-ot/dd-trace-ot.gradle @@ -1,4 +1,5 @@ plugins { + id "com.github.johnrengelman.shadow" version "5.2.0" id "me.champeau.gradle.jmh" version "0.5.0" } @@ -32,6 +33,8 @@ dependencies { implementation deps.autoservice compile project(':dd-trace-api') + compile project(':utils:thread-utils') + compile deps.opentracing compile group: 'io.opentracing.contrib', name: 'opentracing-tracerresolver', version: '0.1.0' @@ -49,7 +52,6 @@ dependencies { testImplementation deps.autoservice testCompile project(":dd-java-agent:testing") - testCompile project(':utils:gc-utils') testCompile group: 'com.github.stefanbirkner', name: 'system-rules', version: '1.17.1' testCompile group: 'org.msgpack', name: 'jackson-dataformat-msgpack', version: '0.8.20' @@ -82,6 +84,23 @@ dependencies { test.finalizedBy ot31CompatabilityTest test.finalizedBy ot33CompatabilityTest +jar { + archiveClassifier = 'unbundled' +} + +shadowJar { + archiveClassifier = '' + + dependencies { + include(project(':utils:thread-utils')) + } +} + +// We don't want bundled dependencies to show up in the pom. +modifyPom { + dependencies.removeAll { it.artifactId == "thread-utils" } +} + jmh { // include = [".*URLAsResourceNameBenchmark"] // include = ['some regular expression'] // include pattern (regular expression) for benchmarks to be executed diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java b/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java index f2fcda7384..548a338e0c 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java @@ -839,6 +839,7 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace private final WeakReference reference; private ShutdownHook(final DDTracer tracer) { + super("dd-tracer-shutdown-hook"); reference = new WeakReference<>(tracer); } diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/PendingTrace.java b/dd-trace-ot/src/main/java/datadog/opentracing/PendingTrace.java index fcf6a771c7..3ceaff45d1 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/PendingTrace.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/PendingTrace.java @@ -1,5 +1,7 @@ package datadog.opentracing; +import static datadog.common.exec.SharedExecutors.scheduleTaskAtFixedRate; + import datadog.opentracing.scopemanager.ContinuableScope; import datadog.trace.common.util.Clock; import java.io.Closeable; @@ -15,9 +17,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedDeque; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -253,6 +252,7 @@ public class PendingTrace extends ConcurrentLinkedDeque { expireReference(); } if (count > 0) { + // TODO attempt to flatten and report if top level spans are finished. (for accurate metrics) log.debug( "trace {} : {} unfinished spans garbage collected. Trace will not report.", traceId, @@ -302,24 +302,12 @@ public class PendingTrace extends ConcurrentLinkedDeque { private static class SpanCleaner implements Runnable, Closeable { private static final long CLEAN_FREQUENCY = 1; - private static final ThreadFactory FACTORY = - new ThreadFactory() { - @Override - public Thread newThread(final Runnable r) { - final Thread thread = new Thread(r, "dd-span-cleaner"); - thread.setDaemon(true); - return thread; - } - }; - - private final ScheduledExecutorService executorService = - Executors.newScheduledThreadPool(1, FACTORY); private final Set pendingTraces = Collections.newSetFromMap(new ConcurrentHashMap()); public SpanCleaner() { - executorService.scheduleAtFixedRate(this, 0, CLEAN_FREQUENCY, TimeUnit.SECONDS); + scheduleTaskAtFixedRate(this, 0, CLEAN_FREQUENCY, TimeUnit.SECONDS); } @Override @@ -331,13 +319,6 @@ public class PendingTrace extends ConcurrentLinkedDeque { @Override public void close() { - executorService.shutdownNow(); - try { - executorService.awaitTermination(500, TimeUnit.MILLISECONDS); - } catch (final InterruptedException e) { - log.info("Writer properly closed and async writer interrupted."); - } - // Make sure that whatever was left over gets cleaned up run(); } diff --git a/dd-trace-ot/src/main/java/datadog/trace/common/writer/ddagent/AbstractDisruptor.java b/dd-trace-ot/src/main/java/datadog/trace/common/writer/ddagent/AbstractDisruptor.java index 86c7fca315..8d5ad7c0e0 100644 --- a/dd-trace-ot/src/main/java/datadog/trace/common/writer/ddagent/AbstractDisruptor.java +++ b/dd-trace-ot/src/main/java/datadog/trace/common/writer/ddagent/AbstractDisruptor.java @@ -4,9 +4,9 @@ import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.SleepingWaitStrategy; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.dsl.ProducerType; +import datadog.common.exec.DaemonThreadFactory; import java.io.Closeable; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; @@ -33,7 +33,7 @@ abstract class AbstractDisruptor implements Closeable { disruptor.handleEventsWith(handler); } - protected abstract ThreadFactory getThreadFactory(); + protected abstract DaemonThreadFactory getThreadFactory(); public void start() { disruptor.start(); diff --git a/dd-trace-ot/src/main/java/datadog/trace/common/writer/ddagent/BatchWritingDisruptor.java b/dd-trace-ot/src/main/java/datadog/trace/common/writer/ddagent/BatchWritingDisruptor.java index b02f2d2e25..299718e5a9 100644 --- a/dd-trace-ot/src/main/java/datadog/trace/common/writer/ddagent/BatchWritingDisruptor.java +++ b/dd-trace-ot/src/main/java/datadog/trace/common/writer/ddagent/BatchWritingDisruptor.java @@ -1,14 +1,14 @@ package datadog.trace.common.writer.ddagent; +import static datadog.common.exec.DaemonThreadFactory.TRACE_WRITER; +import static datadog.common.exec.SharedExecutors.scheduleTaskAtFixedRate; + import com.lmax.disruptor.EventHandler; -import datadog.trace.common.util.DaemonThreadFactory; +import datadog.common.exec.DaemonThreadFactory; import datadog.trace.common.writer.DDAgentWriter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; @@ -21,10 +21,6 @@ import lombok.extern.slf4j.Slf4j; public class BatchWritingDisruptor extends AbstractDisruptor { private static final int FLUSH_PAYLOAD_BYTES = 5_000_000; // 5 MB - // TODO: move executor to tracer for sharing with other tasks. - private final ScheduledExecutorService heartbeatExecutor = - Executors.newScheduledThreadPool(1, new DaemonThreadFactory("dd-trace-heartbeat")); - private final DisruptorEvent.HeartbeatTranslator heartbeatTranslator = new DisruptorEvent.HeartbeatTranslator(); @@ -48,13 +44,13 @@ public class BatchWritingDisruptor extends AbstractDisruptor { } } }; - heartbeatExecutor.scheduleAtFixedRate(heartbeat, 100, 100, TimeUnit.MILLISECONDS); + scheduleTaskAtFixedRate(heartbeat, 100, 100, TimeUnit.MILLISECONDS); } } @Override - protected ThreadFactory getThreadFactory() { - return new DaemonThreadFactory("dd-trace-writer"); + protected DaemonThreadFactory getThreadFactory() { + return TRACE_WRITER; } @Override diff --git a/dd-trace-ot/src/main/java/datadog/trace/common/writer/ddagent/DDAgentApi.java b/dd-trace-ot/src/main/java/datadog/trace/common/writer/ddagent/DDAgentApi.java index 64725297aa..16e6b60e60 100644 --- a/dd-trace-ot/src/main/java/datadog/trace/common/writer/ddagent/DDAgentApi.java +++ b/dd-trace-ot/src/main/java/datadog/trace/common/writer/ddagent/DDAgentApi.java @@ -1,5 +1,6 @@ package datadog.trace.common.writer.ddagent; +import static datadog.common.exec.SharedExecutors.taskScheduler; import static datadog.trace.common.serialization.MsgpackFormatWriter.MSGPACK_WRITER; import com.squareup.moshi.JsonAdapter; @@ -17,6 +18,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; +import okhttp3.Dispatcher; import okhttp3.HttpUrl; import okhttp3.MediaType; import okhttp3.OkHttpClient; @@ -263,6 +265,9 @@ public class DDAgentApi { .connectTimeout(HTTP_TIMEOUT, TimeUnit.SECONDS) .writeTimeout(HTTP_TIMEOUT, TimeUnit.SECONDS) .readTimeout(HTTP_TIMEOUT, TimeUnit.SECONDS) + + // We don't do async so this shouldn't matter, but just to be safe... + .dispatcher(new Dispatcher(taskScheduler())) .build(); } diff --git a/dd-trace-ot/src/main/java/datadog/trace/common/writer/ddagent/TraceProcessingDisruptor.java b/dd-trace-ot/src/main/java/datadog/trace/common/writer/ddagent/TraceProcessingDisruptor.java index 67025b67f2..3966e3dcec 100644 --- a/dd-trace-ot/src/main/java/datadog/trace/common/writer/ddagent/TraceProcessingDisruptor.java +++ b/dd-trace-ot/src/main/java/datadog/trace/common/writer/ddagent/TraceProcessingDisruptor.java @@ -1,11 +1,12 @@ package datadog.trace.common.writer.ddagent; +import static datadog.common.exec.DaemonThreadFactory.TRACE_PROCESSOR; + import com.lmax.disruptor.EventHandler; +import datadog.common.exec.DaemonThreadFactory; import datadog.opentracing.DDSpan; -import datadog.trace.common.util.DaemonThreadFactory; import datadog.trace.common.writer.DDAgentWriter; import java.util.List; -import java.util.concurrent.ThreadFactory; import lombok.extern.slf4j.Slf4j; /** @@ -29,8 +30,8 @@ public class TraceProcessingDisruptor extends AbstractDisruptor> { } @Override - protected ThreadFactory getThreadFactory() { - return new DaemonThreadFactory("dd-trace-processor"); + protected DaemonThreadFactory getThreadFactory() { + return TRACE_PROCESSOR; } @Override diff --git a/gradle/java.gradle b/gradle/java.gradle index c5d3f1c40b..71ca89a975 100644 --- a/gradle/java.gradle +++ b/gradle/java.gradle @@ -6,8 +6,12 @@ apply plugin: 'groovy' apply from: "$rootDir/gradle/checkstyle.gradle" apply from: "$rootDir/gradle/codenarc.gradle" -def applyCodeCoverage = !(project.plugins.hasPlugin('com.github.johnrengelman.shadow') - || project.path.startsWith(":dd-java-agent:instrumentation:")) +def applyCodeCoverage = !( + project.path.startsWith(":dd-smoke-tests") || + project.path == ":dd-java-agent" || + project.path == ":dd-java-agent:load-generator" || + project.path.startsWith(":dd-java-agent:benchmark") || + project.path.startsWith(":dd-java-agent:instrumentation")) if (applyCodeCoverage) { apply from: "$rootDir/gradle/jacoco.gradle" diff --git a/settings.gradle b/settings.gradle index d649c059f2..653e5822d6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -31,8 +31,8 @@ include ':dd-java-agent:load-generator' // misc include ':dd-java-agent:testing' -include ':utils:gc-utils' include ':utils:test-utils' +include ':utils:thread-utils' // smoke tests include ':dd-smoke-tests:cli' diff --git a/utils/gc-utils/src/main/java/datadog/trace/util/gc/GCUtils.java b/utils/test-utils/src/main/java/datadog/trace/util/gc/GCUtils.java similarity index 100% rename from utils/gc-utils/src/main/java/datadog/trace/util/gc/GCUtils.java rename to utils/test-utils/src/main/java/datadog/trace/util/gc/GCUtils.java diff --git a/dd-trace-ot/src/main/java/datadog/trace/common/util/DaemonThreadFactory.java b/utils/thread-utils/src/main/java/datadog/common/exec/DaemonThreadFactory.java similarity index 63% rename from dd-trace-ot/src/main/java/datadog/trace/common/util/DaemonThreadFactory.java rename to utils/thread-utils/src/main/java/datadog/common/exec/DaemonThreadFactory.java index 25d08887ce..fab427b96e 100644 --- a/dd-trace-ot/src/main/java/datadog/trace/common/util/DaemonThreadFactory.java +++ b/utils/thread-utils/src/main/java/datadog/common/exec/DaemonThreadFactory.java @@ -1,9 +1,15 @@ -package datadog.trace.common.util; +package datadog.common.exec; import java.util.concurrent.ThreadFactory; /** A {@link ThreadFactory} implementation that starts all {@link Thread} as daemons. */ public final class DaemonThreadFactory implements ThreadFactory { + public static final DaemonThreadFactory TRACE_PROCESSOR = + new DaemonThreadFactory("dd-trace-processor"); + public static final DaemonThreadFactory TRACE_WRITER = new DaemonThreadFactory("dd-trace-writer"); + public static final DaemonThreadFactory TASK_SCHEDULER = + new DaemonThreadFactory("dd-task-scheduler"); + private final String threadName; /** diff --git a/utils/thread-utils/src/main/java/datadog/common/exec/SharedExecutors.java b/utils/thread-utils/src/main/java/datadog/common/exec/SharedExecutors.java new file mode 100644 index 0000000000..aaa75045b6 --- /dev/null +++ b/utils/thread-utils/src/main/java/datadog/common/exec/SharedExecutors.java @@ -0,0 +1,56 @@ +package datadog.common.exec; + +import static datadog.common.exec.DaemonThreadFactory.TASK_SCHEDULER; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public final class SharedExecutors { + private static final long SHUTDOWN_WAIT_SECONDS = 5; + + private static final ScheduledExecutorService TASK_SCHEDULER_EXECUTOR_SERVICE = + Executors.newSingleThreadScheduledExecutor(TASK_SCHEDULER); + + static { + try { + Runtime.getRuntime().addShutdownHook(new ShutdownCallback(TASK_SCHEDULER_EXECUTOR_SERVICE)); + } catch (final IllegalStateException ex) { + // The JVM is already shutting down. + } + } + + public static ScheduledExecutorService taskScheduler() { + return TASK_SCHEDULER_EXECUTOR_SERVICE; + } + + public static ScheduledFuture scheduleTaskAtFixedRate( + final Runnable command, final long initialDelay, final long period, final TimeUnit unit) { + return TASK_SCHEDULER_EXECUTOR_SERVICE.scheduleAtFixedRate(command, initialDelay, period, unit); + } + + public static boolean isTaskSchedulerShutdown() { + return TASK_SCHEDULER_EXECUTOR_SERVICE.isShutdown(); + } + + private static final class ShutdownCallback extends Thread { + + private final ScheduledExecutorService executorService; + + private ShutdownCallback(final ScheduledExecutorService executorService) { + super("dd-exec-shutdown-hook"); + this.executorService = executorService; + } + + @Override + public void run() { + try { + executorService.shutdown(); + executorService.awaitTermination(SHUTDOWN_WAIT_SECONDS, TimeUnit.SECONDS); + } catch (final InterruptedException e) { + // Don't bother waiting then... + } + } + } +} diff --git a/utils/gc-utils/gc-utils.gradle b/utils/thread-utils/thread-utils.gradle similarity index 100% rename from utils/gc-utils/gc-utils.gradle rename to utils/thread-utils/thread-utils.gradle