Skip to content

Commit 64322a7

Browse files
committed
Skip over unavailable resources
1 parent 1ffca7b commit 64322a7

File tree

2 files changed

+106
-8
lines changed

2 files changed

+106
-8
lines changed

junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ConcurrentHierarchicalTestExecutorService.java

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -226,13 +226,26 @@ void processQueueEntries(WorkerLease workerLease, BooleanSupplier doneCondition)
226226
LOGGER.trace(() -> "yielding resource lock");
227227
break;
228228
}
229-
var entry = workQueue.poll();
230-
if (entry == null) {
231-
LOGGER.trace(() -> "no queue entry available");
229+
var queueEntries = workQueue.peekAll();
230+
if (queueEntries.isEmpty()) {
231+
LOGGER.trace(() -> "no queue entries available");
232232
break;
233233
}
234-
LOGGER.trace(() -> "processing: " + entry.task);
235-
execute(entry);
234+
var queueEntriesByResult = tryToStealWorkWithoutBlocking(queueEntries);
235+
maybeTryToStealWorkWithBlocking(queueEntriesByResult);
236+
}
237+
}
238+
239+
private void maybeTryToStealWorkWithBlocking(Map<WorkStealResult, List<WorkQueue.Entry>> queueEntriesByResult) {
240+
if (queueEntriesByResult.containsKey(WorkStealResult.EXECUTED_BY_THIS_WORKER) || //
241+
queueEntriesByResult.containsKey(WorkStealResult.EXECUTED_BY_DIFFERENT_WORKER)) {
242+
// Queue changed. Try to see if there is work that does not need locking
243+
return;
244+
}
245+
// All resources locked, start blocking
246+
var entriesRequiringResourceLocks = queueEntriesByResult.remove(WorkStealResult.RESOURCE_LOCK_UNAVAILABLE);
247+
if (entriesRequiringResourceLocks != null) {
248+
tryToStealWork(entriesRequiringResourceLocks.get(0), BlockingMode.BLOCKING);
236249
}
237250
}
238251

@@ -557,9 +570,13 @@ private Entry doAdd(Entry entry) {
557570
return entry;
558571
}
559572

560-
@Nullable
561-
Entry poll() {
562-
return queue.poll();
573+
private List<WorkQueue.Entry> peekAll() {
574+
List<Entry> entries = new ArrayList<>(queue);
575+
// Iteration order isn't the same as queue order.
576+
// TODO: This makes the queue kinda pointless
577+
// TODO: This also makes retries pointless
578+
entries.sort(naturalOrder());
579+
return entries;
563580
}
564581

565582
boolean remove(Entry entry) {

platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ConcurrentHierarchicalTestExecutorServiceTests.java

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,87 @@ void runsTasksWithoutConflictingLocksConcurrently() throws Exception {
227227
assertThat(leaves).allSatisfy(TestTaskStub::assertExecutedSuccessfully);
228228
}
229229

230+
231+
@Test
232+
void processingQueueEntriesSkipsOverUnavailableResources() throws Exception {
233+
service = new ConcurrentHierarchicalTestExecutorService(configuration(2));
234+
235+
var resourceLock = new SingleLock(exclusiveResource(LockMode.READ_WRITE), new ReentrantLock());
236+
237+
var lockFreeChildrenStarted = new CountDownLatch(2);
238+
var child1Started = new CountDownLatch(1);
239+
240+
Executable child1Behaviour = () -> {
241+
child1Started.countDown();
242+
lockFreeChildrenStarted.await();
243+
};
244+
Executable child4Behaviour = () -> {
245+
lockFreeChildrenStarted.countDown();
246+
child1Started.await();
247+
};
248+
249+
var child1 = new TestTaskStub(ExecutionMode.CONCURRENT, child1Behaviour) //
250+
.withResourceLock(resourceLock) //
251+
.withName("child1");
252+
var child2 = new TestTaskStub(ExecutionMode.CONCURRENT, lockFreeChildrenStarted::countDown)
253+
.withName("child2"); //
254+
var child3 = new TestTaskStub(ExecutionMode.CONCURRENT)
255+
.withResourceLock(resourceLock) //
256+
.withName("child3");
257+
var child4 = new TestTaskStub(ExecutionMode.CONCURRENT, child4Behaviour)
258+
.withName("child4");
259+
var children = List.of(child1, child2, child3, child4);
260+
var root = new TestTaskStub(ExecutionMode.CONCURRENT, () -> requiredService().invokeAll(children)) //
261+
.withName("root");
262+
263+
service.submit(root).get();
264+
265+
root.assertExecutedSuccessfully();
266+
assertThat(children).allSatisfy(TestTaskStub::assertExecutedSuccessfully);
267+
assertThat(child4.executionThread).isEqualTo(child2.executionThread);
268+
assertThat(child3.startTime).isAfterOrEqualTo(child2.startTime);
269+
}
270+
271+
@Test
272+
void invokeAllQueueEntriesSkipsOverUnavailableResources() throws Exception {
273+
service = new ConcurrentHierarchicalTestExecutorService(configuration(2));
274+
275+
var resourceLock = new SingleLock(exclusiveResource(LockMode.READ_WRITE), new ReentrantLock());
276+
277+
var lockFreeChildrenStarted = new CountDownLatch(2);
278+
var child4Started = new CountDownLatch(1);
279+
280+
Executable child1Behaviour = () -> {
281+
lockFreeChildrenStarted.countDown();
282+
child4Started.await();
283+
};
284+
Executable child4Behaviour = () -> {
285+
child4Started.countDown();
286+
lockFreeChildrenStarted.await();
287+
};
288+
289+
var child1 = new TestTaskStub(ExecutionMode.CONCURRENT, child1Behaviour) //
290+
.withName("child1");
291+
var child2 = new TestTaskStub(ExecutionMode.CONCURRENT)
292+
.withResourceLock(resourceLock) //
293+
.withName("child2"); //
294+
var child3 = new TestTaskStub(ExecutionMode.CONCURRENT, lockFreeChildrenStarted::countDown)
295+
.withName("child3");
296+
var child4 = new TestTaskStub(ExecutionMode.CONCURRENT, child4Behaviour)
297+
.withResourceLock(resourceLock) //
298+
.withName("child4");
299+
var children = List.of(child1, child2, child3, child4);
300+
var root = new TestTaskStub(ExecutionMode.CONCURRENT, () -> requiredService().invokeAll(children)) //
301+
.withName("root");
302+
303+
service.submit(root).get();
304+
305+
root.assertExecutedSuccessfully();
306+
assertThat(children).allSatisfy(TestTaskStub::assertExecutedSuccessfully);
307+
assertThat(child1.executionThread).isEqualTo(child3.executionThread);
308+
assertThat(child2.startTime).isAfterOrEqualTo(child3.startTime);
309+
}
310+
230311
@Test
231312
void prioritizesChildrenOfStartedContainers() throws Exception {
232313
service = new ConcurrentHierarchicalTestExecutorService(configuration(2));

0 commit comments

Comments
 (0)