1
1
/*
2
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3
3
*/
4
4
5
5
package kotlinx.coroutines.guava
@@ -302,7 +302,8 @@ private class ListenableFutureCoroutine<T>(
302
302
) : AbstractCoroutine<T>(context) {
303
303
304
304
// JobListenableFuture propagates external cancellation to `this` coroutine. See JobListenableFuture.
305
- @JvmField val future = JobListenableFuture <T >(this )
305
+ @JvmField
306
+ val future = JobListenableFuture <T >(this )
306
307
307
308
override fun onCompleted (value : T ) {
308
309
future.complete(value)
@@ -347,6 +348,17 @@ private class JobListenableFuture<T>(private val jobToCancel: Job): ListenableFu
347
348
*/
348
349
private val auxFuture = SettableFuture .create<Any >()
349
350
351
+ /* *
352
+ * `true` if [auxFuture.get][ListenableFuture.get] throws [ExecutionException].
353
+ *
354
+ * Note: this is eventually consistent with the state of [auxFuture].
355
+ *
356
+ * Unfortunately, there's no API to figure out if [ListenableFuture] throws [ExecutionException]
357
+ * apart from calling [ListenableFuture.get] on it. To avoid unnecessary [ExecutionException] allocation
358
+ * we use this field as an optimization.
359
+ */
360
+ private var auxFutureIsFailed: Boolean = false
361
+
350
362
/* *
351
363
* When the attached coroutine [isCompleted][Job.isCompleted] successfully
352
364
* its outcome should be passed to this method.
@@ -366,7 +378,8 @@ private class JobListenableFuture<T>(private val jobToCancel: Job): ListenableFu
366
378
// CancellationException is wrapped into `Cancelled` to preserve original cause and message.
367
379
// All the other exceptions are delegated to SettableFuture.setException.
368
380
fun completeExceptionallyOrCancel (t : Throwable ): Boolean =
369
- if (t is CancellationException ) auxFuture.set(Cancelled (t)) else auxFuture.setException(t)
381
+ if (t is CancellationException ) auxFuture.set(Cancelled (t))
382
+ else auxFuture.setException(t).also { if (it) auxFutureIsFailed = true }
370
383
371
384
/* *
372
385
* Returns cancellation _in the sense of [Future]_. This is _not_ equivalent to
@@ -385,7 +398,16 @@ private class JobListenableFuture<T>(private val jobToCancel: Job): ListenableFu
385
398
// this Future hasn't itself been successfully cancelled, the Future will return
386
399
// isCancelled() == false. This is the only discovered way to reconcile the two different
387
400
// cancellation contracts.
388
- return auxFuture.isCancelled || (isDone && Uninterruptibles .getUninterruptibly(auxFuture) is Cancelled )
401
+ return auxFuture.isCancelled || isDone && ! auxFutureIsFailed && try {
402
+ Uninterruptibles .getUninterruptibly(auxFuture) is Cancelled
403
+ } catch (e: CancellationException ) {
404
+ // `auxFuture` got cancelled right after `auxFuture.isCancelled` returned false.
405
+ true
406
+ } catch (e: ExecutionException ) {
407
+ // `auxFutureIsFailed` hasn't been updated yet.
408
+ auxFutureIsFailed = true
409
+ false
410
+ }
389
411
}
390
412
391
413
/* *
@@ -455,7 +477,7 @@ private class JobListenableFuture<T>(private val jobToCancel: Job): ListenableFu
455
477
try {
456
478
when (val result = Uninterruptibles .getUninterruptibly(auxFuture)) {
457
479
is Cancelled -> append(" CANCELLED, cause=[${result.exception} ]" )
458
- else -> append(" SUCCESS, result=[$result " )
480
+ else -> append(" SUCCESS, result=[$result ] " )
459
481
}
460
482
} catch (e: CancellationException ) {
461
483
// `this` future was cancelled by `Future.cancel`. In this case there's no cause or message.
@@ -469,6 +491,7 @@ private class JobListenableFuture<T>(private val jobToCancel: Job): ListenableFu
469
491
} else {
470
492
append(" PENDING, delegate=[$auxFuture ]" )
471
493
}
494
+ append(' ]' )
472
495
}
473
496
}
474
497
0 commit comments