From b31f2c825d0c39de89b68c60e1d502e0e0c0f083 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 29 Jun 2022 17:32:23 +0200 Subject: [PATCH 1/2] Reduce reachable references of disposed invokeOnTimeout handle Fixes #3351 --- .../native/src/MultithreadedDispatchers.kt | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt b/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt index d991fc169e..36722e3823 100644 --- a/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt +++ b/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt @@ -35,9 +35,24 @@ internal class WorkerDispatcher(name: String) : CloseableCoroutineDispatcher(), } override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle { - // No API to cancel on timeout - worker.executeAfter(timeMillis.toMicrosSafe()) { block.run() } - return NonDisposableHandle + // Workers don't have an API to cancel sent "executeAfter" block, but we are trying + // to control the damage and reduce reachable objects by nulling out `block` + // that may retain a lot of references, and leaving only an empty shell after a timely disposal + class DisposableBlock(block: Runnable) : DisposableHandle, Function0 { + private val disposableHolder = AtomicReference(block) + + override fun invoke() { + disposableHolder.value?.run() + } + + override fun dispose() { + disposableHolder.value = null + } + } + + val disposableBlock = DisposableBlock(block) + worker.executeAfter(timeMillis.toMicrosSafe(), disposableBlock) + return disposableBlock } override fun close() { From 77ef1b72674d3bfd8bb6beeffeadce1efc81c07e Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 30 Jun 2022 16:29:22 +0300 Subject: [PATCH 2/2] Update kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt Co-authored-by: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> --- kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt b/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt index 36722e3823..1c1306c291 100644 --- a/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt +++ b/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt @@ -38,6 +38,7 @@ internal class WorkerDispatcher(name: String) : CloseableCoroutineDispatcher(), // Workers don't have an API to cancel sent "executeAfter" block, but we are trying // to control the damage and reduce reachable objects by nulling out `block` // that may retain a lot of references, and leaving only an empty shell after a timely disposal + // This is a class and not an object with `block` in a closure because that would defeat the purpose. class DisposableBlock(block: Runnable) : DisposableHandle, Function0 { private val disposableHolder = AtomicReference(block)