11/*
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.
33 */
44
55package kotlinx.coroutines.guava
@@ -302,7 +302,8 @@ private class ListenableFutureCoroutine<T>(
302302) : AbstractCoroutine<T>(context) {
303303
304304 // JobListenableFuture propagates external cancellation to `this` coroutine. See JobListenableFuture.
305- @JvmField val future = JobListenableFuture <T >(this )
305+ @JvmField
306+ val future = JobListenableFuture <T >(this )
306307
307308 override fun onCompleted (value : T ) {
308309 future.complete(value)
@@ -347,6 +348,17 @@ private class JobListenableFuture<T>(private val jobToCancel: Job): ListenableFu
347348 */
348349 private val auxFuture = SettableFuture .create<Any >()
349350
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+
350362 /* *
351363 * When the attached coroutine [isCompleted][Job.isCompleted] successfully
352364 * its outcome should be passed to this method.
@@ -366,7 +378,8 @@ private class JobListenableFuture<T>(private val jobToCancel: Job): ListenableFu
366378 // CancellationException is wrapped into `Cancelled` to preserve original cause and message.
367379 // All the other exceptions are delegated to SettableFuture.setException.
368380 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 }
370383
371384 /* *
372385 * 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
385398 // this Future hasn't itself been successfully cancelled, the Future will return
386399 // isCancelled() == false. This is the only discovered way to reconcile the two different
387400 // 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+ }
389411 }
390412
391413 /* *
@@ -455,7 +477,7 @@ private class JobListenableFuture<T>(private val jobToCancel: Job): ListenableFu
455477 try {
456478 when (val result = Uninterruptibles .getUninterruptibly(auxFuture)) {
457479 is Cancelled -> append(" CANCELLED, cause=[${result.exception} ]" )
458- else -> append(" SUCCESS, result=[$result " )
480+ else -> append(" SUCCESS, result=[$result ] " )
459481 }
460482 } catch (e: CancellationException ) {
461483 // `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
469491 } else {
470492 append(" PENDING, delegate=[$auxFuture ]" )
471493 }
494+ append(' ]' )
472495 }
473496}
474497
0 commit comments