Skip to content

Commit 5ace22b

Browse files
committed
Better lifecycles of tool daemons
1 parent ddfeac9 commit 5ace22b

File tree

5 files changed

+114
-23
lines changed

5 files changed

+114
-23
lines changed

build.gradle

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -235,14 +235,14 @@ dependencies {
235235
compileOnly cLibs.bundles.compileonly
236236
annotationProcessor cLibs.bundles.annotationprocessor
237237

238-
implementation 'info.picocli:picocli:4.7.6'
239-
implementation 'org.slf4j:slf4j-simple:2.0.13'
240-
implementation 'org.ow2.asm:asm:9.7'
241-
implementation 'dev.lukebemish.mirror.net.fabricmc:mapping-io:0.6.2'
238+
implementation libs.picocli.core
239+
implementation libs.slf4j.simple
240+
implementation libs.asm
241+
implementation libs.mappingio
242242
// For reading/writing parchment json files
243-
implementation "org.parchmentmc:feather:1.1.0"
244-
implementation "org.parchmentmc.feather:io-gson:1.1.0"
245-
annotationProcessor 'info.picocli:picocli-codegen:4.7.6'
243+
implementation libs.feather.core
244+
implementation libs.feather.gson
245+
annotationProcessor libs.picocli.codegen
246246
implementation(project(':')) {
247247
capabilities {
248248
requireCapability("dev.lukebemish:taskgraphrunner-model")
@@ -253,32 +253,32 @@ dependencies {
253253
requireCapability("dev.lukebemish:taskgraphrunner-signatures")
254254
}
255255
}
256-
modelApi 'com.google.code.gson:gson:2.11.0'
256+
modelApi libs.gson
257257
modelCompileOnlyApi cLibs.jspecify
258258
modelCompileOnly cLibs.bundles.compileonly
259259
modelAnnotationProcessor cLibs.bundles.annotationprocessor
260260

261-
signaturesApi 'org.ow2.asm:asm:9.7'
261+
signaturesApi libs.asm
262262
signaturesCompileOnly cLibs.bundles.compileonly
263263
signaturesAnnotationProcessor cLibs.bundles.annotationprocessor
264264

265265
executionCompileOnly cLibs.bundles.compileonly
266266
executionAnnotationProcessor cLibs.bundles.annotationprocessor
267-
executionImplementation('dev.lukebemish:forkedtaskexecutor:0.1.1') {
267+
executionImplementation(libs.forkedtaskexecutor) {
268268
capabilities {
269269
requireFeature('runner')
270270
}
271271
}
272272

273-
implementation 'dev.lukebemish:forkedtaskexecutor:0.1.1'
274-
implementation('dev.lukebemish:forkedtaskexecutor:0.1.1') {
273+
implementation libs.forkedtaskexecutor
274+
implementation(libs.forkedtaskexecutor) {
275275
capabilities {
276276
requireFeature('runner')
277277
}
278278
}
279-
modelApi 'dev.lukebemish:forkedtaskexecutor:0.1.1'
279+
modelApi libs.forkedtaskexecutor
280280

281-
instrumentationImplementation 'org.ow2.asm:asm:9.7'
281+
instrumentationImplementation libs.asm
282282
}
283283

284284
// This keeps the same versions at runtime and compile time

gradle/libs.versions.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[versions]
2+
3+
forkedtaskexecutor = "0.1.3"
4+
picocli = "4.7.6"
5+
slf4j = "2.0.13"
6+
asm = "9.7"
7+
mappingio = "0.6.2"
8+
feather = "1.1.0"
9+
gson = "2.11.0"
10+
11+
[libraries]
12+
13+
forkedtaskexecutor = { group = "dev.lukebemish", name = "forkedtaskexecutor", version.ref = "forkedtaskexecutor" }
14+
picocli-core = { group = "info.picocli", name = "picocli", version.ref = "picocli" }
15+
picocli-codegen = { group = "info.picocli", name = "picocli-codegen", version.ref = "picocli" }
16+
17+
slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" }
18+
19+
asm = { group = "org.ow2.asm", name = "asm", version.ref = "asm" }
20+
21+
mappingio = { group = "dev.lukebemish.mirror.net.fabricmc", name = "mapping-io", version.ref = "mappingio" }
22+
23+
feather-core = { group = "org.parchmentmc", name = "feather", version.ref = "feather" }
24+
feather-gson = { group = "org.parchmentmc.feather", name = "io-gson", version.ref = "feather" }
25+
26+
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }

src/execution/java/dev/lukebemish/taskgraphrunner/execution/ToolTask.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.net.URLClassLoader;
1818
import java.nio.charset.StandardCharsets;
1919
import java.nio.file.Paths;
20+
import java.time.Duration;
2021
import java.util.Arrays;
2122
import java.util.HashSet;
2223
import java.util.Set;
@@ -65,6 +66,35 @@ public PrintStream replaceSystemErr(PrintStream err) {
6566
return new PrintStream(OutputStream.nullOutputStream());
6667
}
6768

69+
@Override
70+
public void setupLifecycleWatcher(Supplier<Integer> currentTasks, Supplier<Boolean> attemptShutdown) {
71+
var thread = new Thread("TaskGraphRunner Tool Daemon Watcher") {
72+
@Override
73+
public void run() {
74+
int emptyFor = 0;
75+
while (true) {
76+
try {
77+
Thread.sleep(Duration.ofSeconds(1));
78+
} catch (InterruptedException e) {
79+
break;
80+
}
81+
if (currentTasks.get() == 0) {
82+
emptyFor++;
83+
} else {
84+
emptyFor = 0;
85+
}
86+
if (emptyFor > 10) {
87+
if (attemptShutdown.get()) {
88+
break;
89+
}
90+
}
91+
}
92+
}
93+
};
94+
thread.setDaemon(true);
95+
thread.start();
96+
}
97+
6898
private record Target(URL[] urls, String mainClass) {}
6999

70100
private int execute(String[] args, Supplier<Target> targetSupplier, String logFile) {

src/main/java/dev/lukebemish/taskgraphrunner/runtime/execution/ToolDaemonExecutor.java

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import dev.lukebemish.taskgraphrunner.runtime.Context;
77
import dev.lukebemish.taskgraphrunner.runtime.util.FileUtils;
88
import dev.lukebemish.taskgraphrunner.runtime.util.HashUtils;
9+
import org.jspecify.annotations.Nullable;
910
import org.objectweb.asm.ClassReader;
1011
import org.objectweb.asm.ClassVisitor;
1112
import org.objectweb.asm.ClassWriter;
@@ -19,6 +20,7 @@
1920
import java.io.File;
2021
import java.io.IOException;
2122
import java.io.UncheckedIOException;
23+
import java.lang.ref.WeakReference;
2224
import java.nio.ByteBuffer;
2325
import java.nio.charset.StandardCharsets;
2426
import java.nio.file.Files;
@@ -31,7 +33,10 @@
3133
import java.util.List;
3234
import java.util.Map;
3335
import java.util.Objects;
36+
import java.util.Queue;
3437
import java.util.concurrent.ConcurrentHashMap;
38+
import java.util.concurrent.ConcurrentLinkedQueue;
39+
import java.util.function.Consumer;
3540
import java.util.jar.JarInputStream;
3641
import java.util.jar.JarOutputStream;
3742
import java.util.stream.Collectors;
@@ -69,13 +74,13 @@ public static void execute(Collection<Path> classpath, String mainClass, Path lo
6974
private static final Map<String, ToolDaemonExecutor> CLASSPATH_INSTANCES = new ConcurrentHashMap<>();
7075

7176
private final ForkedTaskExecutor executor;
72-
private final Runnable onRemoval;
77+
private final Consumer<ToolDaemonExecutor> onRemoval;
7378

74-
private ToolDaemonExecutor(Runnable onRemoval) throws IOException {
79+
private ToolDaemonExecutor(Consumer<ToolDaemonExecutor> onRemoval) throws IOException {
7580
this(new Path[0], onRemoval);
7681
}
7782

78-
private ToolDaemonExecutor(Path[] classpath, Runnable onRemoval) throws IOException {
83+
private ToolDaemonExecutor(Path[] classpath, Consumer<ToolDaemonExecutor> onRemoval) throws IOException {
7984
this.onRemoval = onRemoval;
8085
var workingDirectory = Files.createTempDirectory("taskgraphrunner");
8186
var tempFile = workingDirectory.resolve("execution-daemon.jar");
@@ -110,11 +115,14 @@ private ToolDaemonExecutor(Path[] classpath, Runnable onRemoval) throws IOExcept
110115
.addJvmOption("-cp")
111116
.addJvmOption("@"+ classpathFile.toAbsolutePath())
112117
.taskClass("dev.lukebemish.taskgraphrunner.execution.ToolTask")
118+
.onShutdownRequest(() -> onRemoval.accept(this)) // When the daemon thinks it should be shut down, we stop any new requests from going to it.
113119
.build();
114120

115121
this.executor = new ForkedTaskExecutor(spec);
116122
}
117123

124+
private static final Queue<WeakReference<ToolDaemonExecutor>> TO_CLOSE = new ConcurrentLinkedQueue<>();
125+
118126
static {
119127
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
120128
if (INSTANCE != null) {
@@ -123,30 +131,54 @@ private ToolDaemonExecutor(Path[] classpath, Runnable onRemoval) throws IOExcept
123131
}
124132
CLASSPATH_INSTANCES.values().forEach(ToolDaemonExecutor::close);
125133
CLASSPATH_INSTANCES.clear();
134+
WeakReference<ToolDaemonExecutor> ref;
135+
while ((ref = TO_CLOSE.poll()) != null) {
136+
var executor = ref.get();
137+
if (executor != null) {
138+
executor.close();
139+
}
140+
}
126141
}));
127142
}
128143

129144
@Override
130145
public void close() {
131-
onRemoval.run();
146+
onRemoval.accept(this);
132147
executor.close();
133148
}
134149

135150
private static synchronized ToolDaemonExecutor getInstance(Path[] classpath) {
136-
var key = String.join(File.pathSeparator, Arrays.stream(classpath).map(it -> it.toAbsolutePath().toString()).toArray(CharSequence[]::new));
151+
var key = key(classpath);
137152
return CLASSPATH_INSTANCES.computeIfAbsent(key, k -> {
138153
try {
139-
return new ToolDaemonExecutor(classpath, () -> CLASSPATH_INSTANCES.remove(key));
154+
return new ToolDaemonExecutor(classpath, it -> clearInstance(it, classpath));
140155
} catch (IOException e) {
141156
throw new RuntimeException(e);
142157
}
143158
});
144159
}
145160

161+
private static String key(Path[] classpath) {
162+
return String.join(File.pathSeparator, Arrays.stream(classpath).map(it -> it.toAbsolutePath().toString()).toArray(CharSequence[]::new));
163+
}
164+
165+
private static synchronized void clearInstance(ToolDaemonExecutor it, Path @Nullable [] classpath) {
166+
if (classpath == null && INSTANCE == it) {
167+
var oldInstance = INSTANCE;
168+
TO_CLOSE.add(new WeakReference<>(oldInstance));
169+
INSTANCE = null;
170+
} else {
171+
var key = key(classpath);
172+
if (CLASSPATH_INSTANCES.remove(key, it)) {
173+
TO_CLOSE.add(new WeakReference<>(it));
174+
}
175+
}
176+
}
177+
146178
private static synchronized ToolDaemonExecutor getInstance() {
147179
if (INSTANCE == null) {
148180
try {
149-
INSTANCE = new ToolDaemonExecutor(() -> INSTANCE = null);
181+
INSTANCE = new ToolDaemonExecutor(it -> clearInstance(it, null));
150182
} catch (IOException e) {
151183
throw new RuntimeException(e);
152184
}

src/model/java/dev/lukebemish/taskgraphrunner/model/conversion/NeoFormGenerator.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ public static Config convert(Path neoFormConfig, Value selfReference, Options op
322322
}
323323
List<Argument> args;
324324
TaskModel tool;
325-
if (function.jvmArgs().isEmpty()) {
325+
if (!function.jvmArgs().isEmpty()) {
326326
TaskModel.Tool toolModel;
327327
tool = toolModel = new TaskModel.Tool(step.name(), List.of());
328328

@@ -547,7 +547,9 @@ private static Argument processArgument(Map<String, Input> inputs, String arg, N
547547
var function = fullConfig.functions().get(step.type());
548548
if (isVineflower(function)) {
549549
arg = arg.replace("TRACE", "WARN");
550-
} else if (arg.startsWith("-Xmx")) {
550+
}
551+
552+
if (arg.startsWith("-Xmx")) {
551553
var rest = arg.substring(4);
552554
var systemProp = "dev.lukebemish.taskgraphrunner."+step.type()+".maxHeap";
553555
return new Argument.Untracked("-Xmx{}", new InputValue.DirectInput(new Value.SystemPropertyValue(systemProp, rest)));
@@ -560,6 +562,7 @@ private static Argument processArgument(Map<String, Input> inputs, String arg, N
560562
var systemProp = "dev.lukebemish.taskgraphrunner."+step.type()+".maxThreads";
561563
return new Argument.Untracked("-thr={}", new InputValue.DirectInput(new Value.SystemPropertyValue(systemProp, rest)));
562564
}
565+
563566
return new Argument.ValueInput(null, new InputValue.DirectInput(new Value.DirectStringValue(arg)));
564567
}
565568
}

0 commit comments

Comments
 (0)