Skip to content

Commit e139dee

Browse files
authored
NoSQL: adopt AsyncExecTestBase for cancelled tasks (apache#121)
It seems it is legit that a cancelled periodic tasks can have one of two valid outcomes: a cancelled `CompletableFuture` or a "plain" completed one.
1 parent 41ac277 commit e139dee

File tree

2 files changed

+60
-39
lines changed

2 files changed

+60
-39
lines changed

persistence/nosql/async/api/src/main/java/org/apache/polaris/async/Cancelable.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@
2323
public interface Cancelable<R> {
2424
/**
2525
* Attempt to cancel the delayed execution of a callable. Already running callables are not
26-
* interrupted. A callable may still be invoked after calling this function, as of side effects
27-
* and race conditions.
26+
* interrupted. A callable may still be invoked after calling this function, because of side
27+
* effects and race conditions.
28+
*
29+
* <p>After cancellation, the result of this instance's might be either in state "completed
30+
* exceptionally" ({@link java.util.concurrent.CancellationException}) or successfully completed.
2831
*/
2932
void cancel();
3033

persistence/nosql/async/api/src/testFixtures/java/org/apache/polaris/async/AsyncExecTestBase.java

Lines changed: 55 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.concurrent.atomic.AtomicBoolean;
3333
import java.util.concurrent.atomic.AtomicInteger;
3434
import java.util.stream.IntStream;
35+
import org.assertj.core.api.CompletableFutureAssert;
3536
import org.assertj.core.api.SoftAssertions;
3637
import org.assertj.core.api.junit.jupiter.InjectSoftAssertions;
3738
import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
@@ -86,26 +87,27 @@ public void simpleTests() throws Exception {
8687
.describedAs("initialDelay %s", initialDelay)
8788
.isTrue();
8889
cancelable.cancel();
89-
soft.assertThat(cancelable.completionStage())
90-
.failsWithin(asyncTimeout)
91-
.withThrowableThat()
92-
.isInstanceOf(CancellationException.class);
90+
cancelledAssert(soft.assertThat(cancelable.completionStage()));
9391
}
9492

9593
// .cancel() before execution
9694
var cancelable = executor.schedulePeriodic(() -> {}, Duration.ofHours(12));
9795
cancelable.cancel();
98-
soft.assertThat(cancelable.completionStage())
99-
.failsWithin(asyncTimeout)
100-
.withThrowableThat()
101-
.isInstanceOf(CancellationException.class);
96+
cancelledAssert(soft.assertThat(cancelable.completionStage()));
10297

10398
cancelable = executor.schedule(() -> {}, Duration.ofHours(12));
10499
cancelable.cancel();
105-
soft.assertThat(cancelable.completionStage())
106-
.failsWithin(asyncTimeout)
107-
.withThrowableThat()
108-
.isInstanceOf(CancellationException.class);
100+
cancelledAssert(soft.assertThat(cancelable.completionStage()));
101+
}
102+
103+
private void cancelledAssert(CompletableFutureAssert<?> assertion) {
104+
assertion.satisfiesAnyOf(
105+
completionStage ->
106+
assertThat(completionStage)
107+
.failsWithin(asyncTimeout)
108+
.withThrowableThat()
109+
.isInstanceOf(CancellationException.class),
110+
completionStage -> assertThat(completionStage).succeedsWithin(asyncTimeout));
109111
}
110112

111113
@Test
@@ -120,7 +122,7 @@ public void applicationScopedInvocation() {
120122
public void submitMany() {
121123
int numTasks = 50;
122124
var sem = new Semaphore(0);
123-
var completables =
125+
var completableFutures =
124126
IntStream.range(0, numTasks)
125127
.mapToObj(
126128
i ->
@@ -135,19 +137,19 @@ public void submitMany() {
135137
.map(CompletionStage::toCompletableFuture)
136138
.toList();
137139

138-
soft.assertThat(completables).noneMatch(CompletableFuture::isDone);
140+
soft.assertThat(completableFutures).noneMatch(CompletableFuture::isDone);
139141

140142
sem.release(numTasks);
141143

142-
soft.assertThat(completables)
144+
soft.assertThat(completableFutures)
143145
.allSatisfy(cf -> assertThat(cf).succeedsWithin(asyncTimeout).isEqualTo("foo"));
144146
}
145147

146148
@Test
147149
public void submitManyFailing() {
148150
int numTasks = 50;
149151
var sem = new Semaphore(0);
150-
var completables =
152+
var completableFutures =
151153
IntStream.range(0, numTasks)
152154
.mapToObj(
153155
i ->
@@ -162,18 +164,27 @@ public void submitManyFailing() {
162164
.map(CompletionStage::toCompletableFuture)
163165
.toList();
164166

165-
soft.assertThat(completables).noneMatch(CompletableFuture::isDone);
167+
soft.assertThat(completableFutures).noneMatch(CompletableFuture::isDone);
166168

167169
sem.release(numTasks);
168170

169-
soft.assertThat(completables).allSatisfy(cf -> assertThat(cf).failsWithin(asyncTimeout));
171+
soft.assertThat(completableFutures)
172+
.allSatisfy(
173+
cf ->
174+
assertThat(cf)
175+
.failsWithin(asyncTimeout)
176+
.withThrowableThat()
177+
.isInstanceOf(ExecutionException.class)
178+
.havingCause()
179+
.isInstanceOf(RuntimeException.class)
180+
.withMessage("FAILED"));
170181
}
171182

172183
@Test
173184
public void scheduleMany() {
174185
var numTasks = 50;
175186
var sem = new Semaphore(0);
176-
var completables =
187+
var completableFutures =
177188
IntStream.range(0, numTasks)
178189
.mapToObj(
179190
i ->
@@ -189,11 +200,11 @@ public void scheduleMany() {
189200
.map(CompletionStage::toCompletableFuture)
190201
.toList();
191202

192-
soft.assertThat(completables).noneMatch(CompletableFuture::isDone);
203+
soft.assertThat(completableFutures).noneMatch(CompletableFuture::isDone);
193204

194205
sem.release(numTasks);
195206

196-
soft.assertThat(completables)
207+
soft.assertThat(completableFutures)
197208
.allSatisfy(cf -> assertThat(cf).succeedsWithin(asyncTimeout).isEqualTo("foo"));
198209
}
199210

@@ -218,7 +229,7 @@ public void periodic(int failAfter) {
218229
var dontFail = failAfter == Integer.MAX_VALUE;
219230
var numTasks = 50;
220231
var stop = new AtomicBoolean();
221-
var completables =
232+
var completableFutures =
222233
IntStream.range(0, numTasks)
223234
.mapToObj(
224235
i -> {
@@ -254,11 +265,11 @@ public void periodic(int failAfter) {
254265

255266
var iterations = dontFail ? 10 : (failAfter + 1);
256267
for (int i = 0; i < iterations; i++) {
257-
soft.assertThat(completables).noneMatch(c -> c.f.isDone());
268+
soft.assertThat(completableFutures).noneMatch(c -> c.f.isDone());
258269

259-
completables.forEach(t -> t.before.release());
270+
completableFutures.forEach(t -> t.before.release());
260271

261-
assertThat(completables)
272+
assertThat(completableFutures)
262273
.describedAs("iteration %d", i)
263274
.allSatisfy(
264275
t -> assertThat(t.after.tryAcquire(asyncTimeout.toMillis(), MILLISECONDS)).isTrue());
@@ -267,31 +278,38 @@ public void periodic(int failAfter) {
267278
stop.set(true);
268279

269280
if (dontFail) {
270-
completables.forEach(t -> t.c.cancel());
281+
completableFutures.forEach(t -> t.c.cancel());
271282
}
272283

273-
soft.assertThat(completables).allMatch(t -> t.runs.get() == iterations);
284+
soft.assertThat(completableFutures).allMatch(t -> t.runs.get() == iterations);
274285

275286
// Just in case the periodic tasks get called again, let those run
276-
completables.forEach(s -> s.before.release(1000));
287+
completableFutures.forEach(s -> s.before.release(1000));
277288

278-
soft.assertThat(completables)
289+
soft.assertThat(completableFutures)
279290
.allSatisfy(
280-
t ->
291+
t -> {
292+
if (dontFail) {
293+
cancelledAssert(assertThat(t.f));
294+
} else {
281295
assertThat(t.f)
282296
.completesExceptionallyWithin(asyncTimeout)
283297
.withThrowableThat()
284-
.isInstanceOf(
285-
dontFail ? CancellationException.class : ExecutionException.class));
286-
287-
soft.assertThat(completables).allMatch(t -> t.f.isDone());
298+
.isInstanceOf(ExecutionException.class)
299+
.havingCause()
300+
.isInstanceOf(RuntimeException.class)
301+
.withMessage("FAILED");
302+
}
303+
});
304+
305+
soft.assertThat(completableFutures).allMatch(t -> t.f.isDone());
288306
if (dontFail) {
289-
soft.assertThat(completables).allMatch(t -> t.f.isCancelled());
307+
soft.assertThat(completableFutures).allMatch(t -> t.f.isCancelled());
290308
} else {
291-
soft.assertThat(completables).noneMatch(t -> t.f.isCancelled());
309+
soft.assertThat(completableFutures).noneMatch(t -> t.f.isCancelled());
292310
}
293311
// cancellation is still an exceptional completion
294-
soft.assertThat(completables).allMatch(t -> t.f.isCompletedExceptionally());
312+
soft.assertThat(completableFutures).allMatch(t -> t.f.isCompletedExceptionally());
295313
}
296314

297315
protected abstract void threadAssertion();

0 commit comments

Comments
 (0)