From 4dc588c55c93d710789b80d5868da0d484fc8c3c Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 22 Oct 2020 18:05:38 +0300 Subject: [PATCH 1/8] Fix BlockHound false positives related to channels that use locks Solves https://github.com/Kotlin/kotlinx.coroutines/issues/2302 --- .../src/CoroutinesBlockHoundIntegration.kt | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt index f89d2be23f..da3468047a 100644 --- a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt +++ b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt @@ -1,16 +1,38 @@ @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + package kotlinx.coroutines.debug -import reactor.blockhound.BlockHound import kotlinx.coroutines.scheduling.* +import reactor.blockhound.* import reactor.blockhound.integration.* @Suppress("UNUSED") -public class CoroutinesBlockHoundIntegration: BlockHoundIntegration { +public class CoroutinesBlockHoundIntegration : BlockHoundIntegration { - override fun applyTo(builder: BlockHound.Builder) { - builder.addDynamicThreadPredicate { isSchedulerWorker(it) } - builder.nonBlockingThreadPredicate { p -> p.or { mayNotBlock(it) } } + override fun applyTo(builder: BlockHound.Builder): Unit = with(builder) { + allowBlockingCallsInside("kotlinx.coroutines.channels.AbstractSendChannel", "sendSuspend") + // these classes use a lock internally + for (method in listOf( + "pollInternal", "isEmpty", "isFull", "isClosedForReceive", "offerInternal", "offerSelectInternal", + "enqueueSend", "pollInternal", "pollSelectInternal", "enqueueReceiveInternal", "onCancelIdempotent" )) + { + allowBlockingCallsInside("kotlinx.coroutines.channels.ArrayChannel", method) + } + for (method in listOf("offerInternal", "offerSelectInternal", "updateHead")) { + allowBlockingCallsInside("kotlinx.coroutines.channels.ArrayBroadcastChannel", method) + } + for (method in listOf("checkOffer", "pollInternal", "pollSelectInternal")) { + allowBlockingCallsInside("kotlinx.coroutines.channels.ArrayBroadcastChannel\$Subscriber", method) + } + for (method in listOf("offerInternal", "offerSelectInternal", "pollInternal", "pollSelectInternal", + "onCancelIdempotent")) + { + allowBlockingCallsInside("kotlinx.coroutines.channels.ConflatedChannel", method) + } + // should be safe; used for sending tasks to a thread pool + allowBlockingCallsInside("java.util.concurrent.ScheduledThreadPoolExecutor", "execute") + addDynamicThreadPredicate { isSchedulerWorker(it) } + nonBlockingThreadPredicate { p -> p.or { mayNotBlock(it) } } } } From 611948cda2867a863e77b2257ad9eb72431e3018 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 23 Oct 2020 15:04:51 +0300 Subject: [PATCH 2/8] Fix BlockHound false positives related to use of ServiceLoader Fixes https://github.com/Kotlin/kotlinx.coroutines/issues/2190 --- .../src/CoroutinesBlockHoundIntegration.kt | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt index da3468047a..c3fc130ea7 100644 --- a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt +++ b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt @@ -10,8 +10,7 @@ import reactor.blockhound.integration.* public class CoroutinesBlockHoundIntegration : BlockHoundIntegration { override fun applyTo(builder: BlockHound.Builder): Unit = with(builder) { - allowBlockingCallsInside("kotlinx.coroutines.channels.AbstractSendChannel", "sendSuspend") - // these classes use a lock internally + // These classes use a lock internally, but should be safe to use. for (method in listOf( "pollInternal", "isEmpty", "isFull", "isClosedForReceive", "offerInternal", "offerSelectInternal", "enqueueSend", "pollInternal", "pollSelectInternal", "enqueueReceiveInternal", "onCancelIdempotent" )) @@ -29,8 +28,20 @@ public class CoroutinesBlockHoundIntegration : BlockHoundIntegration { { allowBlockingCallsInside("kotlinx.coroutines.channels.ConflatedChannel", method) } - // should be safe; used for sending tasks to a thread pool + allowBlockingCallsInside("kotlinx.coroutines.channels.AbstractSendChannel", "sendSuspend") + /* This method may block as part of its implementation, but is probably safe. We need to whitelist it so that + it is possible to enqueue coroutines in contexts that use thread pools from other coroutines in a way that's not + considered blocking. */ allowBlockingCallsInside("java.util.concurrent.ScheduledThreadPoolExecutor", "execute") + /* These files have fields that invoke service loaders. They are manually whitelisted; another approach could be + to whitelist the operations performed by service loaders, as they can generally be considered safe. This was not + done here because ServiceLoader has a large API surface, with some methods being hidden as implementation + details (in particular, the implementation of its iterator is completely opaque). Relying on particular names + being used in ServiceLoader's implementation would be brittle. */ + allowBlockingCallsInside("kotlinx.coroutines.reactive.ReactiveFlowKt", "") + allowBlockingCallsInside("kotlinx.coroutines.CoroutineExceptionHandlerImplKt", "") + /* The predicates that define that BlockHound should only report blocking calls from threads that are part of + the coroutine thread pool and currently execute a CPU-bound coroutine computation. */ addDynamicThreadPredicate { isSchedulerWorker(it) } nonBlockingThreadPredicate { p -> p.or { mayNotBlock(it) } } } From 08bcaf2e867a0924bfcaf24b84f3d9ef02629105 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 23 Oct 2020 15:45:45 +0300 Subject: [PATCH 3/8] Partially solve BlockHound false positives on Kotlin Reflection --- .../src/CoroutinesBlockHoundIntegration.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt index c3fc130ea7..38d42713bb 100644 --- a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt +++ b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt @@ -40,6 +40,10 @@ public class CoroutinesBlockHoundIntegration : BlockHoundIntegration { being used in ServiceLoader's implementation would be brittle. */ allowBlockingCallsInside("kotlinx.coroutines.reactive.ReactiveFlowKt", "") allowBlockingCallsInside("kotlinx.coroutines.CoroutineExceptionHandlerImplKt", "") + /* These methods are from the reflection API. The API is big, so surely some other blocking calls will show up, + but with these rules in place, at least some simple examples pass without problems. */ + allowBlockingCallsInside("kotlin.reflect.jvm.internal.impl.builtins.jvm.JvmBuiltInsPackageFragmentProvider", "findPackage") + allowBlockingCallsInside("kotlin.reflect.jvm.internal.impl.resolve.OverridingUtil", "") /* The predicates that define that BlockHound should only report blocking calls from threads that are part of the coroutine thread pool and currently execute a CPU-bound coroutine computation. */ addDynamicThreadPredicate { isSchedulerWorker(it) } From da50c6df1ae3de050ce40a243b7b70c1c769c8cd Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 23 Oct 2020 15:46:52 +0300 Subject: [PATCH 4/8] Add a smoke test for BlockHound ignoring channels --- .../test/BlockHoundTest.kt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/kotlinx-coroutines-debug/test/BlockHoundTest.kt b/kotlinx-coroutines-debug/test/BlockHoundTest.kt index ff5c95cdb1..571daca12f 100644 --- a/kotlinx-coroutines-debug/test/BlockHoundTest.kt +++ b/kotlinx-coroutines-debug/test/BlockHoundTest.kt @@ -1,5 +1,6 @@ package kotlinx.coroutines.debug import kotlinx.coroutines.* +import kotlinx.coroutines.channels.* import org.junit.* import reactor.blockhound.* @@ -52,6 +53,27 @@ class BlockHoundTest : TestBase() { } } + @Test + fun testChannelsNotBeingConsideredBlocking() = runTest { + withContext(Dispatchers.Default) { + // Copy of kotlinx.coroutines.channels.ArrayChannelTest.testSimple + val q = Channel(1) + check(q.isEmpty) + check(!q.isClosedForReceive) + check(!q.isClosedForSend) + val sender = launch { + q.send(1) + q.send(2) + } + val receiver = launch { + q.receive() == 1 + q.receive() == 2 + } + sender.join() + receiver.join() + } + } + @Test(expected = BlockingOperationError::class) fun testReusingThreadsFailure() = runTest { val n = 100 From 59ddd8598d612e42d7d547f280e6a7cfdfb3d8a2 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 23 Oct 2020 17:55:06 +0300 Subject: [PATCH 5/8] Refactor: split BlockHound rules into a few methods --- .../src/CoroutinesBlockHoundIntegration.kt | 78 +++++++++++++++---- 1 file changed, 63 insertions(+), 15 deletions(-) diff --git a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt index 38d42713bb..5371565e90 100644 --- a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt +++ b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt @@ -9,41 +9,89 @@ import reactor.blockhound.integration.* @Suppress("UNUSED") public class CoroutinesBlockHoundIntegration : BlockHoundIntegration { - override fun applyTo(builder: BlockHound.Builder): Unit = with(builder) { - // These classes use a lock internally, but should be safe to use. + /** Allows blocking calls in implementation of channels. + * + * Channels use a lock in their implementation, though only for protecting short pieces of fast and well-understood + * code, so locking there doesn't affect the program liveness. */ + private fun BlockHound.Builder.allowBlockingCallsInChannels() { + allowBlockingCallsInArrayChannel() + allowBlockingCallsInBroadcastChannel() + allowBlockingCallsInConflatedChannel() + } + + /** Allows blocking inside [kotlinx.coroutines.channels.ArrayChannel]. */ + private fun BlockHound.Builder.allowBlockingCallsInArrayChannel() { for (method in listOf( "pollInternal", "isEmpty", "isFull", "isClosedForReceive", "offerInternal", "offerSelectInternal", - "enqueueSend", "pollInternal", "pollSelectInternal", "enqueueReceiveInternal", "onCancelIdempotent" )) + "enqueueSend", "pollInternal", "pollSelectInternal", "enqueueReceiveInternal", "onCancelIdempotent")) { allowBlockingCallsInside("kotlinx.coroutines.channels.ArrayChannel", method) } + } + + /** Allows blocking inside [kotlinx.coroutines.channels.ArrayBroadcastChannel]. */ + private fun BlockHound.Builder.allowBlockingCallsInBroadcastChannel() { for (method in listOf("offerInternal", "offerSelectInternal", "updateHead")) { allowBlockingCallsInside("kotlinx.coroutines.channels.ArrayBroadcastChannel", method) } for (method in listOf("checkOffer", "pollInternal", "pollSelectInternal")) { allowBlockingCallsInside("kotlinx.coroutines.channels.ArrayBroadcastChannel\$Subscriber", method) } + } + + /** Allows blocking inside [kotlinx.coroutines.channels.ConflatedChannel]. */ + private fun BlockHound.Builder.allowBlockingCallsInConflatedChannel() { for (method in listOf("offerInternal", "offerSelectInternal", "pollInternal", "pollSelectInternal", "onCancelIdempotent")) { allowBlockingCallsInside("kotlinx.coroutines.channels.ConflatedChannel", method) } - allowBlockingCallsInside("kotlinx.coroutines.channels.AbstractSendChannel", "sendSuspend") - /* This method may block as part of its implementation, but is probably safe. We need to whitelist it so that - it is possible to enqueue coroutines in contexts that use thread pools from other coroutines in a way that's not - considered blocking. */ + } + + /** Allows blocking when enqueuing tasks into a thread pool. + * + * Without this, the following code breaks: + * ``` + * withContext(Dispatchers.Default) { + * withContext(newSingleThreadContext("singleThreadedContext")) { + * } + * } + * ``` + */ + private fun BlockHound.Builder.allowBlockingWhenEnqueuingTasks() { + /* This method may block as part of its implementation, but is probably safe. */ allowBlockingCallsInside("java.util.concurrent.ScheduledThreadPoolExecutor", "execute") - /* These files have fields that invoke service loaders. They are manually whitelisted; another approach could be - to whitelist the operations performed by service loaders, as they can generally be considered safe. This was not - done here because ServiceLoader has a large API surface, with some methods being hidden as implementation - details (in particular, the implementation of its iterator is completely opaque). Relying on particular names - being used in ServiceLoader's implementation would be brittle. */ + } + + /** Allows instances of [java.util.ServiceLoader] being called. + * + * Each instance is listed separately; another approach could be to generally allow the operations performed by + * service loaders, as they can generally be considered safe. This was not done here because ServiceLoader has a + * large API surface, with some methods being hidden as implementation details (in particular, the implementation of + * its iterator is completely opaque). Relying on particular names being used in ServiceLoader's implementation + * would be brittle, so here we only provide clearance rules for some specific instances. + */ + private fun BlockHound.Builder.allowServiceLoaderInvocationsOnInit() { allowBlockingCallsInside("kotlinx.coroutines.reactive.ReactiveFlowKt", "") allowBlockingCallsInside("kotlinx.coroutines.CoroutineExceptionHandlerImplKt", "") - /* These methods are from the reflection API. The API is big, so surely some other blocking calls will show up, - but with these rules in place, at least some simple examples pass without problems. */ - allowBlockingCallsInside("kotlin.reflect.jvm.internal.impl.builtins.jvm.JvmBuiltInsPackageFragmentProvider", "findPackage") + // not part of the coroutines library, but it would be nice if reflection also wasn't considered blocking allowBlockingCallsInside("kotlin.reflect.jvm.internal.impl.resolve.OverridingUtil", "") + } + + /** Allows some blocking calls from the reflection API. + * + * The API is big, so surely some other blocking calls will show up, but with these rules in place, at least some + * simple examples work without problems. + */ + private fun BlockHound.Builder.allowBlockingCallsInReflectionImpl() { + allowBlockingCallsInside("kotlin.reflect.jvm.internal.impl.builtins.jvm.JvmBuiltInsPackageFragmentProvider", "findPackage") + } + + override fun applyTo(builder: BlockHound.Builder): Unit = with(builder) { + allowBlockingCallsInChannels() + allowBlockingWhenEnqueuingTasks() + allowServiceLoaderInvocationsOnInit() + allowBlockingCallsInReflectionImpl() /* The predicates that define that BlockHound should only report blocking calls from threads that are part of the coroutine thread pool and currently execute a CPU-bound coroutine computation. */ addDynamicThreadPredicate { isSchedulerWorker(it) } From c18c9b1c260f6e9ac6ad35b3dcd04fa6fa2740f6 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 23 Oct 2020 19:39:37 +0300 Subject: [PATCH 6/8] Add other structures to BlockHound's whitelist --- .../src/CoroutinesBlockHoundIntegration.kt | 50 +++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt index 5371565e90..cbd0059f1b 100644 --- a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt +++ b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt @@ -9,10 +9,54 @@ import reactor.blockhound.integration.* @Suppress("UNUSED") public class CoroutinesBlockHoundIntegration : BlockHoundIntegration { - /** Allows blocking calls in implementation of channels. + /** Allows blocking calls in various coroutine structures, such as flows and channels. * - * Channels use a lock in their implementation, though only for protecting short pieces of fast and well-understood - * code, so locking there doesn't affect the program liveness. */ + * They use locks in implementations, though only for protecting short pieces of fast and well-understood code, so + * locking in such places doesn't affect the program liveness. */ + private fun BlockHound.Builder.allowBlockingCallsInPrimitiveImplementations() { + allowBlockingCallsInJobSupport() + allowBlockingCallsInThreadSafeHeap() + allowBlockingCallsInFlow() + allowBlockingCallsInChannels() + } + + private fun BlockHound.Builder.allowBlockingCallsInJobSupport() { + for (method in listOf("finalizeFinishingState", "invokeOnCompletion", "makeCancelling", + "tryMakeCompleting")) + { + allowBlockingCallsInside("kotlinx.coroutines.JobSupport", method) + } + } + + private fun BlockHound.Builder.allowBlockingCallsInThreadSafeHeap() { + for (method in listOf("clear", "peek", "removeFirstOrNull", "addLast")) { + allowBlockingCallsInside("kotlinx.coroutines.internal.ThreadSafeHeap", method) + } + // [addLastIf] is only used in [EventLoop.common]. Users of [removeFirstIf]: + allowBlockingCallsInside("kotlinx.coroutines.test.TestCoroutineDispatcher", "doActionsUntil") + allowBlockingCallsInside("kotlinx.coroutines.test.TestCoroutineContext", "triggerActions") + } + + private fun BlockHound.Builder.allowBlockingCallsInFlow() { + allowBlockingCallsInsideStateFlow() + allowBlockingCallsInsideSharedFlow() + } + + private fun BlockHound.Builder.allowBlockingCallsInsideStateFlow() { + allowBlockingCallsInside("kotlinx.coroutines.flow.StateFlowImpl", "updateState") + } + + private fun BlockHound.Builder.allowBlockingCallsInsideSharedFlow() { + for (method in listOf("emitSuspend", "awaitValue", "getReplayCache", "tryEmit", "cancelEmitter", + "tryTakeValue", "resetReplayCache")) + { + allowBlockingCallsInside("kotlinx.coroutines.flow.SharedFlowImpl", method) + } + for (method in listOf("getSubscriptionCount", "allocateSlot", "freeSlot")) { + allowBlockingCallsInside("kotlinx.coroutines.flow.internal.AbstractSharedFlow", method) + } + } + private fun BlockHound.Builder.allowBlockingCallsInChannels() { allowBlockingCallsInArrayChannel() allowBlockingCallsInBroadcastChannel() From f50b410c130149adc6f76449f2c11900cf6b9113 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 23 Oct 2020 19:43:47 +0300 Subject: [PATCH 7/8] Fix --- .../src/CoroutinesBlockHoundIntegration.kt | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt index cbd0059f1b..d9f24336fc 100644 --- a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt +++ b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt @@ -9,6 +9,17 @@ import reactor.blockhound.integration.* @Suppress("UNUSED") public class CoroutinesBlockHoundIntegration : BlockHoundIntegration { + override fun applyTo(builder: BlockHound.Builder): Unit = with(builder) { + allowBlockingCallsInPrimitiveImplementations() + allowBlockingWhenEnqueuingTasks() + allowServiceLoaderInvocationsOnInit() + allowBlockingCallsInReflectionImpl() + /* The predicates that define that BlockHound should only report blocking calls from threads that are part of + the coroutine thread pool and currently execute a CPU-bound coroutine computation. */ + addDynamicThreadPredicate { isSchedulerWorker(it) } + nonBlockingThreadPredicate { p -> p.or { mayNotBlock(it) } } + } + /** Allows blocking calls in various coroutine structures, such as flows and channels. * * They use locks in implementations, though only for protecting short pieces of fast and well-understood code, so @@ -131,15 +142,4 @@ public class CoroutinesBlockHoundIntegration : BlockHoundIntegration { allowBlockingCallsInside("kotlin.reflect.jvm.internal.impl.builtins.jvm.JvmBuiltInsPackageFragmentProvider", "findPackage") } - override fun applyTo(builder: BlockHound.Builder): Unit = with(builder) { - allowBlockingCallsInChannels() - allowBlockingWhenEnqueuingTasks() - allowServiceLoaderInvocationsOnInit() - allowBlockingCallsInReflectionImpl() - /* The predicates that define that BlockHound should only report blocking calls from threads that are part of - the coroutine thread pool and currently execute a CPU-bound coroutine computation. */ - addDynamicThreadPredicate { isSchedulerWorker(it) } - nonBlockingThreadPredicate { p -> p.or { mayNotBlock(it) } } - } - } From 390faf42e4ef753b2f3b12d845ae54df5b849edc Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Mon, 26 Oct 2020 10:52:03 +0300 Subject: [PATCH 8/8] Fix comment formatting --- .../src/CoroutinesBlockHoundIntegration.kt | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt index d9f24336fc..091e8eb16e 100644 --- a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt +++ b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt @@ -20,10 +20,12 @@ public class CoroutinesBlockHoundIntegration : BlockHoundIntegration { nonBlockingThreadPredicate { p -> p.or { mayNotBlock(it) } } } - /** Allows blocking calls in various coroutine structures, such as flows and channels. + /** + * Allows blocking calls in various coroutine structures, such as flows and channels. * * They use locks in implementations, though only for protecting short pieces of fast and well-understood code, so - * locking in such places doesn't affect the program liveness. */ + * locking in such places doesn't affect the program liveness. + */ private fun BlockHound.Builder.allowBlockingCallsInPrimitiveImplementations() { allowBlockingCallsInJobSupport() allowBlockingCallsInThreadSafeHeap() @@ -31,6 +33,9 @@ public class CoroutinesBlockHoundIntegration : BlockHoundIntegration { allowBlockingCallsInChannels() } + /** + * Allows blocking inside [kotlinx.coroutines.JobSupport]. + */ private fun BlockHound.Builder.allowBlockingCallsInJobSupport() { for (method in listOf("finalizeFinishingState", "invokeOnCompletion", "makeCancelling", "tryMakeCompleting")) @@ -39,6 +44,9 @@ public class CoroutinesBlockHoundIntegration : BlockHoundIntegration { } } + /** + * Allows blocking inside [kotlinx.coroutines.internal.ThreadSafeHeap]. + */ private fun BlockHound.Builder.allowBlockingCallsInThreadSafeHeap() { for (method in listOf("clear", "peek", "removeFirstOrNull", "addLast")) { allowBlockingCallsInside("kotlinx.coroutines.internal.ThreadSafeHeap", method) @@ -53,10 +61,16 @@ public class CoroutinesBlockHoundIntegration : BlockHoundIntegration { allowBlockingCallsInsideSharedFlow() } + /** + * Allows blocking inside the implementation of [kotlinx.coroutines.flow.StateFlow]. + */ private fun BlockHound.Builder.allowBlockingCallsInsideStateFlow() { allowBlockingCallsInside("kotlinx.coroutines.flow.StateFlowImpl", "updateState") } + /** + * Allows blocking inside the implementation of [kotlinx.coroutines.flow.SharedFlow]. + */ private fun BlockHound.Builder.allowBlockingCallsInsideSharedFlow() { for (method in listOf("emitSuspend", "awaitValue", "getReplayCache", "tryEmit", "cancelEmitter", "tryTakeValue", "resetReplayCache")) @@ -74,7 +88,9 @@ public class CoroutinesBlockHoundIntegration : BlockHoundIntegration { allowBlockingCallsInConflatedChannel() } - /** Allows blocking inside [kotlinx.coroutines.channels.ArrayChannel]. */ + /** + * Allows blocking inside [kotlinx.coroutines.channels.ArrayChannel]. + */ private fun BlockHound.Builder.allowBlockingCallsInArrayChannel() { for (method in listOf( "pollInternal", "isEmpty", "isFull", "isClosedForReceive", "offerInternal", "offerSelectInternal", @@ -84,7 +100,9 @@ public class CoroutinesBlockHoundIntegration : BlockHoundIntegration { } } - /** Allows blocking inside [kotlinx.coroutines.channels.ArrayBroadcastChannel]. */ + /** + * Allows blocking inside [kotlinx.coroutines.channels.ArrayBroadcastChannel]. + */ private fun BlockHound.Builder.allowBlockingCallsInBroadcastChannel() { for (method in listOf("offerInternal", "offerSelectInternal", "updateHead")) { allowBlockingCallsInside("kotlinx.coroutines.channels.ArrayBroadcastChannel", method) @@ -94,7 +112,9 @@ public class CoroutinesBlockHoundIntegration : BlockHoundIntegration { } } - /** Allows blocking inside [kotlinx.coroutines.channels.ConflatedChannel]. */ + /** + * Allows blocking inside [kotlinx.coroutines.channels.ConflatedChannel]. + */ private fun BlockHound.Builder.allowBlockingCallsInConflatedChannel() { for (method in listOf("offerInternal", "offerSelectInternal", "pollInternal", "pollSelectInternal", "onCancelIdempotent")) @@ -103,7 +123,8 @@ public class CoroutinesBlockHoundIntegration : BlockHoundIntegration { } } - /** Allows blocking when enqueuing tasks into a thread pool. + /** + * Allows blocking when enqueuing tasks into a thread pool. * * Without this, the following code breaks: * ``` @@ -118,7 +139,8 @@ public class CoroutinesBlockHoundIntegration : BlockHoundIntegration { allowBlockingCallsInside("java.util.concurrent.ScheduledThreadPoolExecutor", "execute") } - /** Allows instances of [java.util.ServiceLoader] being called. + /** + * Allows instances of [java.util.ServiceLoader] being called. * * Each instance is listed separately; another approach could be to generally allow the operations performed by * service loaders, as they can generally be considered safe. This was not done here because ServiceLoader has a @@ -133,7 +155,8 @@ public class CoroutinesBlockHoundIntegration : BlockHoundIntegration { allowBlockingCallsInside("kotlin.reflect.jvm.internal.impl.resolve.OverridingUtil", "") } - /** Allows some blocking calls from the reflection API. + /** + * Allows some blocking calls from the reflection API. * * The API is big, so surely some other blocking calls will show up, but with these rules in place, at least some * simple examples work without problems.