From 7dc3f8830a6bac28b3107258118f89d9f3042166 Mon Sep 17 00:00:00 2001 From: Matthew Nelson Date: Tue, 9 Apr 2024 09:28:09 -0400 Subject: [PATCH 1/5] Add a variable timeout to JS implementation of port utils --- .../tor/runtime/core/util/PortUtilBaseTest.kt | 9 +--- .../kmp/tor/runtime/core/internal/JsNet.kt | 2 +- .../tor/runtime/core/internal/JsPlatform.kt | 8 ---- .../kmp/tor/runtime/core/util/PortUtil.kt | 47 +++++++++++++------ .../runtime/core/util/PortUtilJsUnitTest.kt | 13 ++++- .../core/util/PortUtilNonJsUnitTest.kt | 2 +- 6 files changed, 47 insertions(+), 34 deletions(-) diff --git a/library/runtime-core/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilBaseTest.kt b/library/runtime-core/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilBaseTest.kt index 4d357a0d8..172f58d9b 100644 --- a/library/runtime-core/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilBaseTest.kt +++ b/library/runtime-core/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilBaseTest.kt @@ -29,7 +29,7 @@ import kotlin.time.Duration.Companion.seconds @OptIn(ExperimentalStdlibApi::class) abstract class PortUtilBaseTest { - protected abstract fun openServerSocket( + protected abstract suspend fun openServerSocket( ipAddress: IPAddress, port: Int, ): AutoCloseable @@ -84,7 +84,7 @@ abstract class PortUtilBaseTest { // Ensure any exceptions/results are propagated withContext(Dispatchers.Default) { - delay(5_000.milliseconds) + delay(2_500.milliseconds) } // If it threw an IOException, that would be propagated @@ -114,11 +114,6 @@ abstract class PortUtilBaseTest { socket.close() } catch (_: Throwable) {} } - withContext(Dispatchers.Default) { - // Need to switch context here for an actual delay - // b/c JS needs to establish the connection - delay(10.milliseconds) - } return socket to portProxy } } diff --git a/library/runtime-core/src/jsMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/internal/JsNet.kt b/library/runtime-core/src/jsMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/internal/JsNet.kt index e830e66d9..f62cba504 100644 --- a/library/runtime-core/src/jsMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/internal/JsNet.kt +++ b/library/runtime-core/src/jsMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/internal/JsNet.kt @@ -31,5 +31,5 @@ internal external fun net_createServer(connectionListener: (socket: dynamic) -> @OptIn(InternalProcessApi::class) internal external class net_Server: events_EventEmitter { fun close() - fun listen(port: Int, host: String, backlog: Int, callback: () -> Unit) + fun listen(options: dynamic, callback: () -> Unit) } diff --git a/library/runtime-core/src/jsMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/internal/JsPlatform.kt b/library/runtime-core/src/jsMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/internal/JsPlatform.kt index 1026804dd..30f2c0051 100644 --- a/library/runtime-core/src/jsMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/internal/JsPlatform.kt +++ b/library/runtime-core/src/jsMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/internal/JsPlatform.kt @@ -44,11 +44,3 @@ internal inline fun net_Server.onError( ) { on("error", callback) } - -@Suppress("NOTHING_TO_INLINE") -@OptIn(InternalProcessApi::class) -internal inline fun net_Server.onListening( - noinline callback: () -> Unit, -) { - on("listening", callback) -} diff --git a/library/runtime-core/src/jsMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtil.kt b/library/runtime-core/src/jsMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtil.kt index ee3641e50..652f95fea 100644 --- a/library/runtime-core/src/jsMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtil.kt +++ b/library/runtime-core/src/jsMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtil.kt @@ -25,8 +25,8 @@ import io.matthewnelson.kmp.tor.runtime.core.internal.* import io.matthewnelson.kmp.tor.runtime.core.internal.PortProxyIterator.Companion.iterator import io.matthewnelson.kmp.tor.runtime.core.internal.net_createServer import io.matthewnelson.kmp.tor.runtime.core.internal.onError -import io.matthewnelson.kmp.tor.runtime.core.internal.onListening import kotlinx.coroutines.* +import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlin.time.TimeSource @@ -40,7 +40,7 @@ import kotlin.time.TimeSource // @Throws(IOException::class, CancellationException::class) public actual suspend fun Port.isAvailableAsync( host: LocalHost, -): Boolean = host.resolve().isPortAvailable(value) +): Boolean = host.resolve().isPortAvailable(value, timeout = 100.milliseconds) /** * Finds an available TCP port on [LocalHost] starting with the current @@ -64,16 +64,35 @@ public actual suspend fun Port.Proxy.findAvailableAsync( val ipAddress = host.resolve() val ctx = currentCoroutineContext() - while (ctx.isActive && i.hasNext()) { - if (!ipAddress.isPortAvailable(i.next())) continue + val maxTimeouts = 5 + var timeouts = 0 + while (ctx.isActive && i.hasNext() && timeouts < maxTimeouts) { + try { + val isAvailable = ipAddress.isPortAvailable(i.next()) + timeouts = 0 + if (!isAvailable) continue + } catch (_: IOException) { + timeouts++ + continue + } + return i.toPortProxy() } - throw ctx.cancellationExceptionOr { i.unavailableException(ipAddress) } + throw ctx.cancellationExceptionOr { + if (timeouts >= maxTimeouts) { + IOException("$maxTimeouts successive timeouts occurred when checking availability") + } else { + i.unavailableException(ipAddress) + } + } } // @Throws(IOException::class, CancellationException::class) -private suspend fun IPAddress.isPortAvailable(port: Int): Boolean { +private suspend fun IPAddress.isPortAvailable( + port: Int, + timeout: Duration = 42.milliseconds +): Boolean { val timeMark = TimeSource.Monotonic.markNow() val ctx = currentCoroutineContext() val latch = Job(ctx[Job]) @@ -87,11 +106,6 @@ private suspend fun IPAddress.isPortAvailable(port: Int): Boolean { latch.invokeOnCompletion { server.close() } - server.onListening { - isAvailable = true - latch.complete() - } - server.onError { err -> if ((err.code as String) == "EADDRINUSE") { isAvailable = false @@ -101,13 +115,16 @@ private suspend fun IPAddress.isPortAvailable(port: Int): Boolean { latch.complete() } - server.listen(port, ipAddress, 1) { + val options = js("{}") + options["port"] = port + options["host"] = ipAddress + options["backlog"] = 1 + + server.listen(options) { isAvailable = true latch.complete() } - val waitTime = (if (IsUnixLikeHost) 42 else 84).milliseconds - withContext(NonCancellable) { while ( ctx.isActive @@ -116,7 +133,7 @@ private suspend fun IPAddress.isPortAvailable(port: Int): Boolean { && error == null ) { delay(5.milliseconds) - if (timeMark.elapsedNow() > waitTime) break + if (timeMark.elapsedNow() > timeout) break } } } finally { diff --git a/library/runtime-core/src/jsTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilJsUnitTest.kt b/library/runtime-core/src/jsTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilJsUnitTest.kt index 9a47311a8..9b44eeec9 100644 --- a/library/runtime-core/src/jsTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilJsUnitTest.kt +++ b/library/runtime-core/src/jsTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilJsUnitTest.kt @@ -18,18 +18,27 @@ package io.matthewnelson.kmp.tor.runtime.core.util import io.matthewnelson.kmp.tor.runtime.core.address.IPAddress import io.matthewnelson.kmp.tor.runtime.core.internal.net_createServer import io.matthewnelson.kmp.tor.runtime.core.internal.onError +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext import kotlin.test.fail +import kotlin.time.Duration.Companion.milliseconds @OptIn(ExperimentalStdlibApi::class) class PortUtilJsUnitTest: PortUtilBaseTest() { - override fun openServerSocket( + override suspend fun openServerSocket( ipAddress: IPAddress, port: Int, ): AutoCloseable { val server = net_createServer { it.destroy(); Unit } server.onError { err -> fail(err.toString()) } - server.listen(port, ipAddress.value, 1) {} + val options = js("{}") + options["port"] = port + options["host"] = ipAddress.value + options["backlog"] = 1 + server.listen(options) {} + withContext(Dispatchers.Default) { delay(10.milliseconds) } return object : AutoCloseable { override fun close() { server.close() } } diff --git a/library/runtime-core/src/nonJsTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilNonJsUnitTest.kt b/library/runtime-core/src/nonJsTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilNonJsUnitTest.kt index dee371322..bffcad558 100644 --- a/library/runtime-core/src/nonJsTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilNonJsUnitTest.kt +++ b/library/runtime-core/src/nonJsTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilNonJsUnitTest.kt @@ -23,7 +23,7 @@ import io.matthewnelson.kmp.tor.runtime.core.internal.ServerSocketProducer.Compa @OptIn(ExperimentalStdlibApi::class) class PortUtilNonJsUnitTest: PortUtilBaseTest() { - override fun openServerSocket( + override suspend fun openServerSocket( ipAddress: IPAddress, port: Int, ): AutoCloseable = ipAddress.toServerSocketProducer() From 8321d974670e2484090157709ef72e22287c0a7b Mon Sep 17 00:00:00 2001 From: Matthew Nelson Date: Tue, 9 Apr 2024 09:44:53 -0400 Subject: [PATCH 2/5] Add some additional buffer time for start/stopping of tor --- .../kmp/tor/runtime/core/util/PortUtilBaseTest.kt | 4 +++- .../kmp/tor/runtime/core/util/PortUtilJsUnitTest.kt | 2 ++ .../io/matthewnelson/kmp/tor/runtime/ctrl/TestUtils.kt | 4 ++-- .../kmp/tor/runtime/ctrl/TorCtrlFactoryUnitTest.kt | 6 +++--- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/library/runtime-core/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilBaseTest.kt b/library/runtime-core/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilBaseTest.kt index 172f58d9b..001d373c9 100644 --- a/library/runtime-core/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilBaseTest.kt +++ b/library/runtime-core/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilBaseTest.kt @@ -29,6 +29,8 @@ import kotlin.time.Duration.Companion.seconds @OptIn(ExperimentalStdlibApi::class) abstract class PortUtilBaseTest { + protected open val isNodeJs: Boolean = false + protected abstract suspend fun openServerSocket( ipAddress: IPAddress, port: Int, @@ -54,7 +56,7 @@ abstract class PortUtilBaseTest { fun givenFindAvailable_whenCoroutineCancelled_thenHandlesCancellationProperly() = runTest(timeout = 120.seconds) { val port = Port.Proxy.MIN.toPortProxy() val host = LocalHost.IPv4 - val limit = 750 + val limit = if (isNodeJs) 50 else 750 val i = port.iterator(limit) var count = 0 diff --git a/library/runtime-core/src/jsTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilJsUnitTest.kt b/library/runtime-core/src/jsTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilJsUnitTest.kt index 9b44eeec9..154daf9dc 100644 --- a/library/runtime-core/src/jsTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilJsUnitTest.kt +++ b/library/runtime-core/src/jsTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilJsUnitTest.kt @@ -27,6 +27,8 @@ import kotlin.time.Duration.Companion.milliseconds @OptIn(ExperimentalStdlibApi::class) class PortUtilJsUnitTest: PortUtilBaseTest() { + override val isNodeJs: Boolean = true + override suspend fun openServerSocket( ipAddress: IPAddress, port: Int, diff --git a/library/runtime-ctrl/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/TestUtils.kt b/library/runtime-ctrl/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/TestUtils.kt index 18cc04703..cd744ec96 100644 --- a/library/runtime-ctrl/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/TestUtils.kt +++ b/library/runtime-ctrl/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/TestUtils.kt @@ -41,7 +41,7 @@ public object TestUtils { val dataDir = homeDir.resolve("data") val cacheDir = homeDir.resolve("cache") - withContext(Dispatchers.Default) { delay(250.milliseconds) } + withContext(Dispatchers.Default) { delay(350.milliseconds) } val p = Process.Builder(paths.tor) .args("--DataDirectory") @@ -73,7 +73,7 @@ public object TestUtils { currentCoroutineContext().job.invokeOnCompletion { p.destroy() } - withContext(Dispatchers.Default) { delay(250.milliseconds) } + withContext(Dispatchers.Default) { delay(350.milliseconds) } return p } diff --git a/library/runtime-ctrl/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/TorCtrlFactoryUnitTest.kt b/library/runtime-ctrl/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/TorCtrlFactoryUnitTest.kt index 6db26dc6b..febb941e0 100644 --- a/library/runtime-ctrl/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/TorCtrlFactoryUnitTest.kt +++ b/library/runtime-ctrl/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/TorCtrlFactoryUnitTest.kt @@ -81,7 +81,7 @@ class TorCtrlFactoryUnitTest { p.destroy() } - withContext(Dispatchers.Default) { delay(250.milliseconds) } + withContext(Dispatchers.Default) { delay(350.milliseconds) } assertTrue(ctrl.isDestroyed()) } @@ -102,7 +102,7 @@ class TorCtrlFactoryUnitTest { ) val host = resolve() - val port = startPort.findAvailableAsync(1_000, this) + val port = startPort.findAvailableAsync(100, this) val address = ProxyAddress(host, port) @@ -114,7 +114,7 @@ class TorCtrlFactoryUnitTest { block(process, ctrl) - withContext(Dispatchers.Default) { delay(250.milliseconds) } + withContext(Dispatchers.Default) { delay(500.milliseconds) } assertEquals(1, invocationDestroy) From d4693b1be1e97f98403fc56aa987ff867e91d048 Mon Sep 17 00:00:00 2001 From: Matthew Nelson Date: Tue, 9 Apr 2024 09:48:35 -0400 Subject: [PATCH 3/5] Use __OwningControllerProcess to mitigate any potentially unmanaged tor processes on test blow ups --- .../kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/TestUtils.kt | 2 ++ .../matthewnelson/kmp/tor/runtime/mobile/TorCtrlFactoryTest.kt | 2 ++ 2 files changed, 4 insertions(+) diff --git a/library/runtime-ctrl/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/TestUtils.kt b/library/runtime-ctrl/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/TestUtils.kt index cd744ec96..19e589f9d 100644 --- a/library/runtime-ctrl/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/TestUtils.kt +++ b/library/runtime-ctrl/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/TestUtils.kt @@ -64,6 +64,8 @@ public object TestUtils { .args("1") .args("--RunAsDaemon") .args("0") + .args("__OwningControllerProcess") + .args(Process.Current.pid().toString()) .destroySignal(Signal.SIGTERM) .environment("HOME", homeDir.path) .stdin(Stdio.Null) diff --git a/library/runtime-mobile/src/androidInstrumentedTest/kotlin/io/matthewnelson/kmp/tor/runtime/mobile/TorCtrlFactoryTest.kt b/library/runtime-mobile/src/androidInstrumentedTest/kotlin/io/matthewnelson/kmp/tor/runtime/mobile/TorCtrlFactoryTest.kt index 6de139aa9..e3230c141 100644 --- a/library/runtime-mobile/src/androidInstrumentedTest/kotlin/io/matthewnelson/kmp/tor/runtime/mobile/TorCtrlFactoryTest.kt +++ b/library/runtime-mobile/src/androidInstrumentedTest/kotlin/io/matthewnelson/kmp/tor/runtime/mobile/TorCtrlFactoryTest.kt @@ -93,6 +93,8 @@ class TorCtrlFactoryTest { .args("1") .args("--RunAsDaemon") .args("0") + .args("__OwningControllerProcess") + .args(Process.Current.pid().toString()) .destroySignal(Signal.SIGTERM) .environment("HOME", homeDir.path) .stdin(Stdio.Null) From 319d8fd4035477458aafc9afc17c4c51cc55b61a Mon Sep 17 00:00:00 2001 From: Matthew Nelson Date: Tue, 9 Apr 2024 10:04:14 -0400 Subject: [PATCH 4/5] Increase availability limit for Node.js --- .../matthewnelson/kmp/tor/runtime/core/util/PortUtilBaseTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/runtime-core/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilBaseTest.kt b/library/runtime-core/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilBaseTest.kt index 001d373c9..656d02ed1 100644 --- a/library/runtime-core/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilBaseTest.kt +++ b/library/runtime-core/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/util/PortUtilBaseTest.kt @@ -56,7 +56,7 @@ abstract class PortUtilBaseTest { fun givenFindAvailable_whenCoroutineCancelled_thenHandlesCancellationProperly() = runTest(timeout = 120.seconds) { val port = Port.Proxy.MIN.toPortProxy() val host = LocalHost.IPv4 - val limit = if (isNodeJs) 50 else 750 + val limit = if (isNodeJs) 250 else 750 val i = port.iterator(limit) var count = 0 From c566c999e880f4bd6339e4f11646afb00d333ab2 Mon Sep 17 00:00:00 2001 From: Matthew Nelson Date: Tue, 9 Apr 2024 10:56:18 -0400 Subject: [PATCH 5/5] Use Dispatchers.IO outright, without limited parallelism --- .../kmp/tor/runtime/ctrl/TorCtrl.kt | 74 +++++++++++++++---- 1 file changed, 61 insertions(+), 13 deletions(-) diff --git a/library/runtime-ctrl/src/nonJsMain/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/TorCtrl.kt b/library/runtime-ctrl/src/nonJsMain/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/TorCtrl.kt index be361af80..1ba639bf6 100644 --- a/library/runtime-ctrl/src/nonJsMain/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/TorCtrl.kt +++ b/library/runtime-ctrl/src/nonJsMain/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/TorCtrl.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "KotlinRedundantDiagnosticSuppress") package io.matthewnelson.kmp.tor.runtime.ctrl @@ -122,8 +122,12 @@ public actual interface TorCtrl : Destroyable, TorEvent.Processor, TorCmd.Privil * */ @Throws(CancellationException::class, IOException::class) public actual suspend fun connectAsync(address: ProxyAddress): TorCtrl { - return connect { context -> - withContext(context) { address.connect() } + return withDelayedReturnAsync { + connect { context -> + withContext(context) { + address.connect() + } + } } } @@ -138,8 +142,12 @@ public actual interface TorCtrl : Destroyable, TorEvent.Processor, TorCmd.Privil public actual suspend fun connectAsync(path: File): TorCtrl { path.checkUnixSockedSupport() - return connect { context -> - withContext(context) { path.connect() } + return withDelayedReturnAsync { + connect { context -> + withContext(context) { + path.connect() + } + } } } @@ -153,7 +161,9 @@ public actual interface TorCtrl : Destroyable, TorEvent.Processor, TorCmd.Privil * */ @Throws(IOException::class) public fun connect(address: ProxyAddress): TorCtrl { - return connect { address.connect() } + return withDelayedReturn { + connect { address.connect() } + } } /** @@ -170,10 +180,13 @@ public actual interface TorCtrl : Destroyable, TorEvent.Processor, TorCmd.Privil public fun connect(path: File): TorCtrl { path.checkUnixSockedSupport() - return connect { path.connect() } + return withDelayedReturn { + connect { path.connect() } + } } @Throws(IOException::class) + @Suppress("NOTHING_TO_INLINE") @OptIn(ExperimentalContracts::class) private inline fun connect( connect: (context: CoroutineContext) -> CtrlConnection, @@ -182,8 +195,7 @@ public actual interface TorCtrl : Destroyable, TorEvent.Processor, TorCmd.Privil callsInPlace(connect, InvocationKind.EXACTLY_ONCE) } - @OptIn(ExperimentalCoroutinesApi::class) - val dispatcher = Dispatchers.IO.limitedParallelism(2) + val dispatcher = Dispatchers.IO val connection = try { connect(dispatcher) @@ -191,12 +203,24 @@ public actual interface TorCtrl : Destroyable, TorEvent.Processor, TorCmd.Privil throw t.wrapIOException() } - val ctrl = RealTorCtrl.of(this, dispatcher, connection) + return RealTorCtrl.of(this, dispatcher, connection) + } + + /** + * A slight delay is needed before returning in order + * to ensure that the coroutine starts before able + * to call destroy on it. + * */ + @Suppress("NOTHING_TO_INLINE") + @OptIn(ExperimentalContracts::class) + private inline fun withDelayedReturn(block: () -> TorCtrl): TorCtrl { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + + val ctrl = block() try { - // A slight delay is needed before returning in order - // to ensure that the coroutine starts before able - // to call destroy on it. Blocking.threadSleep(25.milliseconds) } catch (e: InterruptedException) { ctrl.destroy() @@ -206,6 +230,30 @@ public actual interface TorCtrl : Destroyable, TorEvent.Processor, TorCmd.Privil return ctrl } + /** + * A slight delay is needed before returning in order + * to ensure that the coroutine starts before able + * to call destroy on it. + * */ + @Suppress("NOTHING_TO_INLINE") + @OptIn(ExperimentalContracts::class) + private suspend inline fun withDelayedReturnAsync(block: () -> TorCtrl): TorCtrl { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + + val ctrl = block() + + try { + delay(25.milliseconds) + } catch (e: CancellationException) { + ctrl.destroy() + throw e + } + + return ctrl + } + @InternalKmpTorApi public actual fun tempQueue(): TempTorCmdQueue = TempTorCmdQueue.of(handler) }