Skip to content

Commit bd2042f

Browse files
committed
Only allow caller owning the DeferredSupplier to run it
1 parent 160a4ed commit bd2042f

File tree

1 file changed

+38
-7
lines changed

1 file changed

+38
-7
lines changed

junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,9 @@ public void close() {
221221
}
222222
var newStoredValue = this.storedValues.compute(compositeKey, //
223223
(__, oldStoredValue) -> {
224-
if (isPresent(oldStoredValue)) {
224+
// guard against race conditions, repeated from getStoredValue
225+
// this filters out failures inserted by computeIfAbsent
226+
if (isStoredValuePresent(oldStoredValue)) {
225227
return oldStoredValue;
226228
}
227229
rejectIfClosed();
@@ -232,6 +234,7 @@ public void close() {
232234
});
233235

234236
if (newStoredValue instanceof StoredValue.DeferredValue value) {
237+
// Any thread that won the race may run the DeferredSupplier
235238
value.delegate().run();
236239
}
237240
return requireNonNull(newStoredValue.evaluate());
@@ -261,23 +264,31 @@ public <K, V> Object computeIfAbsent(N namespace, K key, Function<? super K, ? e
261264
if (result != null) {
262265
return result;
263266
}
267+
Object ownerToken = new Object();
264268
StoredValue newStoredValue = this.storedValues.compute(compositeKey, (__, oldStoredValue) -> {
269+
// guard against race conditions
270+
// computeIfAbsent replaces both null and absent values
265271
if (StoredValue.evaluateIfNotNull(oldStoredValue) != null) {
266272
return oldStoredValue;
267273
}
268274
rejectIfClosed();
269-
return newStoredSuppliedValue(new DeferredSupplier(() -> {
275+
return newStoredSuppliedValue(new DeferredSupplier(ownerToken, () -> {
270276
rejectIfClosed();
271277
return Preconditions.notNull(defaultCreator.apply(key), "defaultCreator must not return null");
272278
}));
273279
});
274280

275281
if (newStoredValue instanceof StoredValue.DeferredOptionalValue value) {
276282
var delegate = value.delegate();
277-
delegate.run();
278-
return requireNonNull(delegate.getOrThrow());
283+
if (ownerToken.equals(delegate.ownerToken())) {
284+
// Only the thread that created the DeferredSupplier may run it
285+
delegate.run();
286+
// Only the thread that caused the exception may see it
287+
return requireNonNull(delegate.getOrThrow());
288+
}
279289
}
280-
// put or getOrComputeIfAbsent won the race
290+
// Either put, getOrComputeIfAbsent, or another computeIfAbsent call
291+
// won the race
281292
return requireNonNull(newStoredValue.evaluate());
282293
}
283294

@@ -409,7 +420,7 @@ private StoredValue.DeferredOptionalValue newStoredSuppliedValue(DeferredSupplie
409420

410421
private @Nullable StoredValue getStoredValue(CompositeKey<N> compositeKey) {
411422
StoredValue storedValue = this.storedValues.get(compositeKey);
412-
if (isPresent(storedValue)) {
423+
if (isStoredValuePresent(storedValue)) {
413424
return storedValue;
414425
}
415426
if (this.parentStore != null) {
@@ -418,7 +429,7 @@ private StoredValue.DeferredOptionalValue newStoredSuppliedValue(DeferredSupplie
418429
return null;
419430
}
420431

421-
private static boolean isPresent(@Nullable StoredValue value) {
432+
private static boolean isStoredValuePresent(@Nullable StoredValue value) {
422433
return value != null && value.isPresent();
423434
}
424435

@@ -473,6 +484,9 @@ private interface StoredValue {
473484
return value != null ? value.evaluate() : null;
474485
}
475486

487+
/**
488+
* May contain {@code null} or a value, never an exception.
489+
*/
476490
record Value(int order, @Nullable Object value) implements StoredValue {
477491

478492
@Override
@@ -486,6 +500,9 @@ public boolean isPresent() {
486500
}
487501
}
488502

503+
/**
504+
* May eventually contain {@code null} or a value or an exception.
505+
*/
489506
record DeferredValue(int order, DeferredSupplier delegate) implements StoredValue {
490507

491508
@Override
@@ -499,6 +516,9 @@ public boolean isPresent() {
499516
}
500517
}
501518

519+
/**
520+
* May eventually contain a value or an exception, never {@code null}.
521+
*/
502522
record DeferredOptionalValue(int order, DeferredSupplier delegate) implements StoredValue {
503523

504524
@Override
@@ -548,11 +568,22 @@ private void close(CloseAction<N> closeAction) throws Throwable {
548568
private static final class DeferredSupplier implements Supplier<@Nullable Object> {
549569

550570
private final FutureTask<@Nullable Object> task;
571+
private final @Nullable Object ownerToken;
551572

552573
DeferredSupplier(Supplier<@Nullable Object> delegate) {
574+
this(null, delegate);
575+
}
576+
577+
DeferredSupplier(@Nullable Object ownerToken, Supplier<@Nullable Object> delegate) {
578+
this.ownerToken = ownerToken;
553579
this.task = new FutureTask<>(delegate::get);
554580
}
555581

582+
@Nullable
583+
Object ownerToken() {
584+
return ownerToken;
585+
}
586+
556587
void run() {
557588
this.task.run();
558589
}

0 commit comments

Comments
 (0)