Skip to content

Commit 8814f41

Browse files
committed
feat: Handle TaskSystem thread die
1 parent 114764b commit 8814f41

File tree

2 files changed

+50
-14
lines changed

2 files changed

+50
-14
lines changed

gdx-box2d-utils/src/main/java/com/badlogic/gdx/box2d/utils/Box2dWorldTaskSystem.java

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.concurrent.ArrayBlockingQueue;
1010
import java.util.concurrent.BlockingQueue;
1111
import java.util.concurrent.CountDownLatch;
12+
import java.util.concurrent.atomic.AtomicInteger;
1213

1314
/**
1415
* This class implements a basic TaskSystem in java, for multithreading of a box2d world.
@@ -24,6 +25,8 @@ public class Box2dWorldTaskSystem implements Disposable {
2425

2526
private final Thread[] workers;
2627
private final int numWorkers;
28+
private final AtomicInteger aliveWorkers;
29+
private final Box2dWorldTaskSystemDeathHandler deathHandler;
2730
private final BlockingQueue<Box2dTaskChunk> taskChunks;
2831
private final CountDownLatch[] countDownLatches = new CountDownLatch[MAX_TASKS];
2932
private final ClosureObject<Box2d.b2EnqueueTaskCallback> enqueueTaskCallback;
@@ -39,36 +42,41 @@ public class Box2dWorldTaskSystem implements Disposable {
3942
*
4043
* @param worldDef The world to configure
4144
* @param numWorkers The amount of worker
45+
* @param deathHandler Handler for when the task system dies. see {@link Box2dWorldTaskSystemDeathHandler}
4246
* @return The created {@link Box2dWorldTaskSystem}
4347
*/
44-
public static Box2dWorldTaskSystem createForWorld(b2WorldDef worldDef, int numWorkers) {
45-
Box2dWorldTaskSystem multiThreader = new Box2dWorldTaskSystem(numWorkers);
48+
public static Box2dWorldTaskSystem createForWorld(b2WorldDef worldDef, int numWorkers, Box2dWorldTaskSystemDeathHandler deathHandler) {
49+
Box2dWorldTaskSystem multiThreader = new Box2dWorldTaskSystem(numWorkers, deathHandler);
4650
multiThreader.configureForWorld(worldDef);
4751
return multiThreader;
4852
}
4953

50-
private Box2dWorldTaskSystem(int numWorkers) {
54+
private Box2dWorldTaskSystem(int numWorkers, Box2dWorldTaskSystemDeathHandler deathHandler) {
5155
if (numWorkers <= 1)
5256
throw new IllegalArgumentException("Number of workers must be greater than 1");
5357
this.numWorkers = numWorkers;
5458
this.workers = new Thread[numWorkers];
5559
this.taskChunks = new ArrayBlockingQueue<>(MAX_TASKS * numWorkers);
60+
this.aliveWorkers = new AtomicInteger(numWorkers);
61+
this.deathHandler = deathHandler;
5662

5763
for (int i = 0; i < numWorkers; i++) {
5864
final int workerId = i;
5965
workers[i] = new Thread(() -> {
60-
while (running) {
61-
try {
62-
Box2dTaskChunk task = taskChunks.take();
66+
try {
67+
while (running) {
6368
try {
69+
Box2dTaskChunk task = taskChunks.take();
6470
task.execute(workerId);
65-
} finally {
6671
countDownLatches[task.taskIndex].countDown();
72+
} catch (InterruptedException e) {
73+
break;
6774
}
68-
} catch (InterruptedException e) {
69-
break;
7075
}
71-
}
76+
} finally {
77+
if (running)
78+
onThreadDies();
79+
}
7280
}, "Box2d-Worker-" + workerId);
7381
workers[i].setDaemon(true);
7482
workers[i].start();
@@ -113,11 +121,17 @@ private Box2dWorldTaskSystem(int numWorkers) {
113121
countDownLatches[taskId].await();
114122
countDownLatches[taskId] = null;
115123
} catch (InterruptedException e) {
116-
throw new RuntimeException(e);
124+
throw new Box2dWorldTaskSystemInterruptException(e);
117125
}
118126
});
119127
}
120128

129+
private void onThreadDies() {
130+
if (aliveWorkers.getAndDecrement() == numWorkers) {
131+
deathHandler.taskSystemDied();
132+
}
133+
}
134+
121135
private void configureForWorld(b2WorldDef worldDef) {
122136
worldDef.enqueueTask(enqueueTaskCallback);
123137
worldDef.finishTask(finishTaskCallback);
@@ -131,7 +145,6 @@ public void afterStep() {
131145
taskCount = 0;
132146
}
133147

134-
135148
/**
136149
* Cleans up all resources. Should not be called before destroying the world.
137150
*/
@@ -171,4 +184,25 @@ public void execute(int workerId) {
171184
task.getClosure().b2TaskCallback_call(start, end, workerId, taskContext);
172185
}
173186
}
187+
188+
public static class Box2dWorldTaskSystemInterruptException extends RuntimeException {
189+
public Box2dWorldTaskSystemInterruptException(InterruptedException e) {
190+
super(e);
191+
}
192+
}
193+
194+
@FunctionalInterface
195+
public interface Box2dWorldTaskSystemDeathHandler {
196+
/**
197+
* So, your task system died, hooray! This was caused by an exception on a worker thread. <br>
198+
* The most important thing to do now is, to call {@link Thread#interrupt()} on the thread, that runs {@link Box2d#b2World_Step}.<br>
199+
* This will make your {@link Box2d#b2World_Step} method throw a {@link Box2dWorldTaskSystemInterruptException}.<br>
200+
* Now you have two options:<br>
201+
* 1. Let everything crash and don't catch the exception <br>
202+
* 2. Catch the exception and handle it gracefully. After you caught the exception, you can safely dispose the current TaskSystem.
203+
* You should also be able to gracefully tear down the world, but that's more risky. In any case, you shouldn't use the world further and create a new one. <br>
204+
* The exception that made the worker thread crash can be seen over the {@link Thread#setDefaultUncaughtExceptionHandler}}
205+
*/
206+
void taskSystemDied();
207+
}
174208
}

gdx-box2d-utils/src/test/java/com/badlogic/gdx/box2d/utils/test/Box2dWorldTaskSystemTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ public static void tiltedStacks(int testIndex, int workerCount, b2Vec2[][] final
2020
worldDef.enableSleep(false);
2121

2222
Box2dWorldTaskSystem multiThreader = null;
23-
if (workerCount > 1)
24-
multiThreader = Box2dWorldTaskSystem.createForWorld(worldDef, workerCount);
23+
if (workerCount > 1) {
24+
Thread currentThread = Thread.currentThread();
25+
multiThreader = Box2dWorldTaskSystem.createForWorld(worldDef, workerCount, currentThread::interrupt);
26+
}
2527

2628
b2WorldId worldId = b2CreateWorld(worldDef.asPointer());
2729
b2BodyId[] bodies = new b2BodyId[e_count];

0 commit comments

Comments
 (0)