diff --git a/kotlinx-coroutines-core/jvm/src/ThreadPoolDispatcher.kt b/kotlinx-coroutines-core/jvm/src/ThreadPoolDispatcher.kt index c385b706e7..96074f5de3 100644 --- a/kotlinx-coroutines-core/jvm/src/ThreadPoolDispatcher.kt +++ b/kotlinx-coroutines-core/jvm/src/ThreadPoolDispatcher.kt @@ -14,5 +14,5 @@ public actual fun newFixedThreadPoolContext(nThreads: Int, name: String): Execut t.isDaemon = true t } - return executor.asCoroutineDispatcher() + return Executors.unconfigurableExecutorService(executor).asCoroutineDispatcher() } diff --git a/kotlinx-coroutines-core/jvm/test/MultithreadedDispatchersJvmTest.kt b/kotlinx-coroutines-core/jvm/test/MultithreadedDispatchersJvmTest.kt new file mode 100644 index 0000000000..b10ab34668 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/test/MultithreadedDispatchersJvmTest.kt @@ -0,0 +1,30 @@ +package kotlinx.coroutines + +import kotlinx.coroutines.internal.LocalAtomicInt +import kotlinx.coroutines.testing.* +import java.util.concurrent.ScheduledThreadPoolExecutor +import kotlin.coroutines.EmptyCoroutineContext +import kotlin.test.* + +class MultithreadedDispatchersJvmTest: TestBase() { + /** Tests that the executor created in [newFixedThreadPoolContext] can not leak and be reconfigured. */ + @Test + fun testExecutorReconfiguration() { + newFixedThreadPoolContext(1, "test").apply { + (executor as? ScheduledThreadPoolExecutor)?.corePoolSize = 2 + }.use { ctx -> + val atomicInt = LocalAtomicInt(0) + repeat(100) { + ctx.dispatch(EmptyCoroutineContext, Runnable { + val entered = atomicInt.incrementAndGet() + Thread.yield() // allow other tasks to run + try { + check(entered == 1) { "Expected only one thread to be used, observed $entered" } + } finally { + atomicInt.decrementAndGet() + } + }) + } + } + } +} diff --git a/kotlinx-coroutines-core/jvm/test/knit/ClosedAfterGuideTestExecutor.kt b/kotlinx-coroutines-core/jvm/test/knit/ClosedAfterGuideTestExecutor.kt index bbe4dc1066..b98bae97aa 100644 --- a/kotlinx-coroutines-core/jvm/test/knit/ClosedAfterGuideTestExecutor.kt +++ b/kotlinx-coroutines-core/jvm/test/knit/ClosedAfterGuideTestExecutor.kt @@ -7,9 +7,6 @@ import kotlin.coroutines.* internal fun newSingleThreadContext(name: String): ExecutorCoroutineDispatcher = ClosedAfterGuideTestDispatcher(1, name) -internal fun newFixedThreadPoolContext(nThreads: Int, name: String): ExecutorCoroutineDispatcher = - ClosedAfterGuideTestDispatcher(nThreads, name) - private class ClosedAfterGuideTestDispatcher( private val nThreads: Int, private val name: String diff --git a/test-utils/jvm/src/FieldWalker.kt b/test-utils/jvm/src/FieldWalker.kt index a807ce64c3..e7303199aa 100644 --- a/test-utils/jvm/src/FieldWalker.kt +++ b/test-utils/jvm/src/FieldWalker.kt @@ -127,6 +127,9 @@ object FieldWalker { element is AtomicLongFieldUpdater<*> -> { /* filter it out here to suppress its subclasses too */ } + element is ExecutorService && type.name == "java.util.concurrent.Executors\$DelegatedExecutorService" -> { + /* can't access anything in the executor */ + } // All the other classes are reflectively scanned else -> fields(type, statics).forEach { field -> push(field.get(element), visited, stack) { Ref.FieldRef(element, field.name) }