While examining source of Kotlin coroutines, I noticed a difference (marked with **
) between JDK 8 CompletableFuture
public fun <T> future(
context: CoroutineContext = DefaultDispatcher,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): CompletableFuture<T> {
require(!start.isLazy) { "$start start is not supported" }
val newContext = newCoroutineContext(context)
val job = Job(newContext[Job])
val future = CompletableFutureCoroutine<T>(newContext + job)
job.cancelFutureOnCompletion(future)
** future.whenComplete { _, exception -> job.cancel(exception) } **
start(block, receiver=future, completion=future) // use the specified start strategy
return future
}
private class CompletableFutureCoroutine<T>(
override val context: CoroutineContext
) : CompletableFuture<T>(), Continuation<T>, CoroutineScope {
override val coroutineContext: CoroutineContext get() = context
override val isActive: Boolean get() = context[Job]!!.isActive
override fun resume(value: T) { complete(value) }
override fun resumeWithException(exception: Throwable) { completeExceptionally(exception) }
** doesn't override cancel which corresponds to interrupt task **
}
and Guava ListenableFuture
public fun <T> future(
context: CoroutineContext = DefaultDispatcher,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): ListenableFuture<T> {
require(!start.isLazy) { "$start start is not supported" }
val newContext = newCoroutineContext(context)
val job = Job(newContext[Job])
val future = ListenableFutureCoroutine<T>(newContext + job)
job.cancelFutureOnCompletion(future)
start(block, receiver=future, completion=future) // use the specified start strategy
return future
}
private class ListenableFutureCoroutine<T>(
override val context: CoroutineContext
) : AbstractFuture<T>(), Continuation<T>, CoroutineScope {
override val coroutineContext: CoroutineContext get() = context
override val isActive: Boolean get() = context[Job]!!.isActive
override fun resume(value: T) { set(value) }
override fun resumeWithException(exception: Throwable) { setException(exception) }
** override fun interruptTask() { context[Job]!!.cancel() } **
}
integrations, though I think of the types as nearly equivalent (except of course ListenableFuture
can't be completed directly, but I don't see why this matters here). Is there a specific reason behind this difference?
The CompletableFuture.cancel is an open (overridable) method, but it is not designed for override. Its documentation does not provide any guarantee for its invocation on cancellation, so the only future-proof (pun intended) way to learn that CompletableFuture
was cancelled is by installing whenComplete
listener on it.
For example, it is perfectly legal for a future version of JDK to add yet another method to cancel a future that does not internally invoke cancel
. Such a change would not violate any piece of CompletableFuture
contract.
Compare this with documentation on AbstractFuture.interruptTask. This method is explicitly designed for override and its documentation guarantees conditions under which it is invoked. Thus, we can provide a slightly more efficient implementation for ListenableFuture
builder that avoids creation of lambda to install a cancellation listener on it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With