diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2021-08-23 23:55:41 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2021-08-23 23:55:41 +0000 |
commit | ffe1b7dc19f3fbe1bc4a5f4f1e28c64cd68c5371 (patch) | |
tree | 747e5be83c6e6a2e2d9a164ebd03c5e45e6ec952 /kotlinx-coroutines-debug/src/junit/CoroutinesTimeoutImpl.kt | |
parent | 2bbecd60d31b5d0b48847f67e741018f5ae7aa07 (diff) | |
parent | 531d55eaeac09ad6cde55687ef802d4235040985 (diff) | |
download | platform_external_kotlinx.coroutines-simpleperf-release.tar.gz platform_external_kotlinx.coroutines-simpleperf-release.tar.bz2 platform_external_kotlinx.coroutines-simpleperf-release.zip |
Snap for 7668063 from 531d55eaeac09ad6cde55687ef802d4235040985 to simpleperf-releasesimpleperf-release
Change-Id: I844c9c61f2e31a265c36a483e1509e0e08c508d6
Diffstat (limited to 'kotlinx-coroutines-debug/src/junit/CoroutinesTimeoutImpl.kt')
-rw-r--r-- | kotlinx-coroutines-debug/src/junit/CoroutinesTimeoutImpl.kt | 81 |
1 files changed, 81 insertions, 0 deletions
diff --git a/kotlinx-coroutines-debug/src/junit/CoroutinesTimeoutImpl.kt b/kotlinx-coroutines-debug/src/junit/CoroutinesTimeoutImpl.kt new file mode 100644 index 00000000..06a84a5b --- /dev/null +++ b/kotlinx-coroutines-debug/src/junit/CoroutinesTimeoutImpl.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.debug + +import java.util.concurrent.* + +/** + * Run [invocation] in a separate thread with the given timeout in ms, after which the coroutines info is dumped and, if + * [cancelOnTimeout] is set, the execution is interrupted. + * + * Assumes that [DebugProbes] are installed. Does not deinstall them. + */ +internal inline fun <T : Any?> runWithTimeoutDumpingCoroutines( + methodName: String, + testTimeoutMs: Long, + cancelOnTimeout: Boolean, + initCancellationException: () -> Throwable, + crossinline invocation: () -> T +): T { + val testStartedLatch = CountDownLatch(1) + val testResult = FutureTask { + testStartedLatch.countDown() + invocation() + } + /* + * We are using hand-rolled thread instead of single thread executor + * in order to be able to safely interrupt thread in the end of a test + */ + val testThread = Thread(testResult, "Timeout test thread").apply { isDaemon = true } + try { + testThread.start() + // Await until test is started to take only test execution time into account + testStartedLatch.await() + return testResult.get(testTimeoutMs, TimeUnit.MILLISECONDS) + } catch (e: TimeoutException) { + handleTimeout(testThread, methodName, testTimeoutMs, cancelOnTimeout, initCancellationException()) + } catch (e: ExecutionException) { + throw e.cause ?: e + } +} + +private fun handleTimeout(testThread: Thread, methodName: String, testTimeoutMs: Long, cancelOnTimeout: Boolean, + cancellationException: Throwable): Nothing { + val units = + if (testTimeoutMs % 1000 == 0L) + "${testTimeoutMs / 1000} seconds" + else "$testTimeoutMs milliseconds" + + System.err.println("\nTest $methodName timed out after $units\n") + System.err.flush() + + DebugProbes.dumpCoroutines() + System.out.flush() // Synchronize serr/sout + + /* + * Order is important: + * 1) Create exception with a stacktrace of hang test + * 2) Cancel all coroutines via debug agent API (changing system state!) + * 3) Throw created exception + */ + cancellationException.attachStacktraceFrom(testThread) + testThread.interrupt() + cancelIfNecessary(cancelOnTimeout) + // If timed out test throws an exception, we can't do much except ignoring it + throw cancellationException +} + +private fun cancelIfNecessary(cancelOnTimeout: Boolean) { + if (cancelOnTimeout) { + DebugProbes.dumpCoroutinesInfo().forEach { + it.job?.cancel() + } + } +} + +private fun Throwable.attachStacktraceFrom(thread: Thread) { + val stackTrace = thread.stackTrace + this.stackTrace = stackTrace +} |