Skip to content

Commit 452ebbc

Browse files
committed
Skip over unavailable resources
1 parent 1ffca7b commit 452ebbc

File tree

2 files changed

+99
-8
lines changed

2 files changed

+99
-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: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,80 @@ void runsTasksWithoutConflictingLocksConcurrently() throws Exception {
227227
assertThat(leaves).allSatisfy(TestTaskStub::assertExecutedSuccessfully);
228228
}
229229

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

0 commit comments

Comments
 (0)