diff --git a/httpcore5/src/main/java/org/apache/hc/core5/concurrent/ComplexCancellable.java b/httpcore5/src/main/java/org/apache/hc/core5/concurrent/ComplexCancellable.java index 1b43219752..eabfc530ad 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/concurrent/ComplexCancellable.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/concurrent/ComplexCancellable.java @@ -53,9 +53,11 @@ public boolean isCancelled() { @Override public void setDependency(final Cancellable dependency) { Args.notNull(dependency, "dependency"); - final Cancellable actualDependency = dependencyRef.getReference(); - if (!dependencyRef.compareAndSet(actualDependency, dependency, false, false)) { - dependency.cancel(); + while (!dependencyRef.compareAndSet(dependencyRef.getReference(), dependency, false, false)) { + if (dependencyRef.isMarked()) { + dependency.cancel(); + return; + } } } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestComplexCancellable.java b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestComplexCancellable.java index 42806b9b84..e728bc6450 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestComplexCancellable.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/concurrent/TestComplexCancellable.java @@ -27,11 +27,13 @@ package org.apache.hc.core5.concurrent; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; import org.hamcrest.CoreMatchers; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.util.concurrent.CountDownLatch; + class TestComplexCancellable { @Test @@ -41,7 +43,7 @@ void testCancelled() { final BasicFuture dependency1 = new BasicFuture<>(null); cancellable.setDependency(dependency1); - Assertions.assertFalse(cancellable.isCancelled()); + assertFalse(cancellable.isCancelled()); cancellable.cancel(); assertThat(cancellable.isCancelled(), CoreMatchers.is(true)); @@ -52,4 +54,25 @@ void testCancelled() { assertThat(dependency2.isCancelled(), CoreMatchers.is(true)); } + @Test + void testSetDependencyRace() throws InterruptedException { + final ComplexCancellable cancellable = new ComplexCancellable(); + final BasicFuture dependency1 = new BasicFuture<>(null); + final BasicFuture dependency2 = new BasicFuture<>(null); + final CountDownLatch latch = new CountDownLatch(2); + + for (int i = 0; i < 2; i++) { + new Thread(() -> { + for (int j = 0; j < 5_000; j++) { + cancellable.setDependency(dependency1); + cancellable.setDependency(dependency2); + } + latch.countDown(); + }).start(); + } + latch.await(); + + assertFalse(dependency1.isCancelled()); + assertFalse(dependency2.isCancelled()); + } }