From 95e32929feedf4dcd1a7615662e3cf27064c9a47 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 5 Sep 2022 18:08:47 +0200 Subject: [PATCH] Properly cancel handles returned by setTimeout in JS dispatchers Otherwise, usage of withTimeout lead to excessive memory pressure and OOMs --- kotlinx-coroutines-core/js/src/JSDispatcher.kt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/kotlinx-coroutines-core/js/src/JSDispatcher.kt b/kotlinx-coroutines-core/js/src/JSDispatcher.kt index 603005d5a4..4b1daae353 100644 --- a/kotlinx-coroutines-core/js/src/JSDispatcher.kt +++ b/kotlinx-coroutines-core/js/src/JSDispatcher.kt @@ -47,7 +47,6 @@ internal sealed class SetTimeoutBasedDispatcher: CoroutineDispatcher(), Delay { override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) { val handle = setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, delayToInt(timeMillis)) - // Actually on cancellation, but clearTimeout is idempotent continuation.invokeOnCancellation(handler = ClearTimeout(handle).asHandler) } } @@ -64,7 +63,7 @@ internal object SetTimeoutDispatcher : SetTimeoutBasedDispatcher() { } } -private class ClearTimeout(private val handle: Int) : CancelHandler(), DisposableHandle { +private open class ClearTimeout(protected val handle: Int) : CancelHandler(), DisposableHandle { override fun dispose() { clearTimeout(handle) @@ -83,15 +82,18 @@ internal class WindowDispatcher(private val window: Window) : CoroutineDispatche override fun dispatch(context: CoroutineContext, block: Runnable) = queue.enqueue(block) override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) { - window.setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, delayToInt(timeMillis)) + val handle = window.setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, delayToInt(timeMillis)) + continuation.invokeOnCancellation(handler = WindowClearTimeout(handle).asHandler) } override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle { val handle = window.setTimeout({ block.run() }, delayToInt(timeMillis)) - return object : DisposableHandle { - override fun dispose() { - window.clearTimeout(handle) - } + return WindowClearTimeout(handle) + } + + private inner class WindowClearTimeout(handle: Int) : ClearTimeout(handle) { + override fun dispose() { + window.clearTimeout(handle) } } }