Skip to content

Instantly share code, notes, and snippets.

@elizarov
Created November 18, 2020 13:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save elizarov/2de3bf1131bc65608836d11e07731819 to your computer and use it in GitHub Desktop.
Save elizarov/2de3bf1131bc65608836d11e07731819 to your computer and use it in GitHub Desktop.
Dump cycles for kotinx-coroutines 1.4.1 running on Kotlin 1.4.20
Index: kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt (revision 4dd4e59f6676a45499cd193aa8fd9521013f8a3f)
+++ kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt (date 1605704376180)
@@ -62,12 +62,19 @@
*/
@Suppress("LeakingThis")
@InternalCoroutinesApi
-public actual open class LockFreeLinkedListNode {
+public actual open class LockFreeLinkedListNode : Dump {
// those _next & _prev refs can be null on Kotlin/Native when doubly-linked list is unlinked
private val _next = atomic<Any?>(this) // Node | Removed | OpDescriptor
private val _prev = atomic<Node?>(this) // Node to the left (cannot be marked as removed)
private val _removedRef = atomic<Removed?>(null) // lazily cached removed ref to this
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ seen.dumpField("prev", _prev.value)
+ seen.dumpField("next", _next.value)
+ }
+
+ open fun dumpNode(seen: ArrayList<Any>) {}
+
private fun removed(): Removed =
_removedRef.value ?: Removed(this).also {
storeCyclicRef { _removedRef.lazySet(it) }
@@ -374,10 +381,17 @@
public open class RemoveFirstDesc<T>(
@JvmField val queue: Node
- ) : AbstractAtomicDesc() {
+ ) : AbstractAtomicDesc(), Dump {
private val _affectedNode = atomic<Node?>(null)
private val _originalNext = atomic<Node?>(null)
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ seen.dumpField("atomicOp", atomicOp)
+ seen.dumpField("affectedNode", _affectedNode.value)
+ seen.dumpField("originalNext", _originalNext.value)
+ seen.dumpField("queue", queue)
+ }
+
@Suppress("UNCHECKED_CAST")
public val result: T get() = affectedNode!! as T
@@ -657,10 +671,14 @@
override fun toString(): String = "$classSimpleName@$hexAddress"
}
-private class Removed(ref: Node) {
+private class Removed(ref: Node) : Dump {
private val wRef: Any = ref.weakRef()
val ref: Node? get() = wRef.unweakRef() as Node?
override fun toString(): String = "Removed[$ref]"
+
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ ref?.dump(seen)
+ }
}
@PublishedApi
Index: kotlinx-coroutines-core/common/src/CancellableContinuation.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- kotlinx-coroutines-core/common/src/CancellableContinuation.kt (revision 4dd4e59f6676a45499cd193aa8fd9521013f8a3f)
+++ kotlinx-coroutines-core/common/src/CancellableContinuation.kt (date 1605702588076)
@@ -382,7 +382,11 @@
override fun toString() = "RemoveOnCancel[$node]"
}
-private class DisposeOnCancel(private val handle: DisposableHandle) : CancelHandler() {
+private class DisposeOnCancel(private val handle: DisposableHandle) : CancelHandler(), Dump {
override fun invoke(cause: Throwable?) = handle.dispose()
override fun toString(): String = "DisposeOnCancel[$handle]"
+
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ seen.dumpField("handle", handle)
+ }
}
Index: kotlinx-coroutines-core/native/src/channels/ArrayBufferState.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- kotlinx-coroutines-core/native/src/channels/ArrayBufferState.kt (revision 4dd4e59f6676a45499cd193aa8fd9521013f8a3f)
+++ kotlinx-coroutines-core/native/src/channels/ArrayBufferState.kt (date 1605704376184)
@@ -6,11 +6,17 @@
import kotlinx.atomicfu.*
import kotlinx.atomicfu.locks.*
+import kotlinx.coroutines.internal.*
-internal actual open class ArrayBufferState actual constructor(initialBufferSize: Int) : SynchronizedObject() {
+internal actual open class ArrayBufferState actual constructor(initialBufferSize: Int) : SynchronizedObject(), Dump {
protected val _buffer = atomic(atomicArrayOfNulls<Any?>(initialBufferSize))
protected val _bufferSize = atomic(initialBufferSize)
+
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ seen.dumpField("buffer", _buffer.value)
+ }
+
actual val bufferSize: Int
get() = _bufferSize.value
Index: kotlinx-coroutines-core/common/src/selects/Select.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- kotlinx-coroutines-core/common/src/selects/Select.kt (revision 4dd4e59f6676a45499cd193aa8fd9521013f8a3f)
+++ kotlinx-coroutines-core/common/src/selects/Select.kt (date 1605704376125)
@@ -235,7 +235,7 @@
internal class SelectBuilderImpl<in R>(
uCont: Continuation<R>
) : LockFreeLinkedListHead(), SelectBuilder<R>,
- SelectInstance<R>, Continuation<R>, CoroutineStackFrame
+ SelectInstance<R>, Continuation<R>, CoroutineStackFrame, Dump
{
private val uCont: Continuation<R> = uCont.asShareable() // unintercepted delegate continuation, shareable
@@ -256,6 +256,13 @@
get() = _parentHandle.value
set(value) { _parentHandle.value = value }
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ super<LockFreeLinkedListHead>.dumpImpl(seen)
+ seen.dumpField("state", _state.value)
+ seen.dumpField("result", _result.value)
+ seen.dumpField("parentHandle", _parentHandle.value)
+ }
+
/* Result state machine
+-----------+ getResult +---------------------+ resume +---------+
@@ -341,6 +348,14 @@
if (trySelect())
resumeSelectWithException(job.getCancellationException())
}
+ override fun toString(): String = "SelectOnCancelling[${this@SelectBuilderImpl}]"
+
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ super.dumpImpl(seen)
+ val p = seen.dumpPrefix()
+ println("$p impl: ${this@SelectBuilderImpl}")
+ this@SelectBuilderImpl.dump(seen)
+ }
}
@PublishedApi
@@ -571,10 +586,15 @@
private class AtomicSelectOp(
@JvmField val impl: SelectBuilderImpl<*>,
@JvmField val desc: AtomicDesc
- ) : AtomicOp<Any?>() {
+ ) : AtomicOp<Any?>(), Dump {
// all select operations are totally ordered by their creating time using selectOpSequenceNumber
override val opSequence = selectOpSequenceNumber.next()
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ seen.dumpField("impl", impl)
+ seen.dumpField("desc", desc)
+ }
+
init {
desc.atomicOp = this
}
@@ -660,9 +680,14 @@
private class DisposeNode(
handle: DisposableHandle
- ) : LockFreeLinkedListNode() {
+ ) : LockFreeLinkedListNode(), Dump {
private val _handle = atomic<DisposableHandle?>(handle)
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ super<LockFreeLinkedListNode>.dumpImpl(seen)
+ seen.dumpField("handle", _handle)
+ }
+
fun dispose() {
val handle = _handle.getAndSet(null)
handle?.dispose()
Index: kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt (revision 4dd4e59f6676a45499cd193aa8fd9521013f8a3f)
+++ kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt (date 1605704376114)
@@ -42,6 +42,12 @@
*/
private val state = ArrayBufferState(capacity)
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ super.dumpImpl(seen)
+ seen.dumpField("state", state)
+ seen.dumpField("subscribers", subscribers)
+ }
+
// head & tail are Long (64 bits) and we assume that they never wrap around
// head, tail, and size are guarded by bufferLock
@@ -207,6 +213,12 @@
) : AbstractChannel<E>(null), ReceiveChannel<E> {
private val subLock = reentrantLock()
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ super.dumpImpl(seen)
+ seen.dumpField("broadcastChannel", broadcastChannel)
+ seen.dumpField("subLock", subLock)
+ }
+
private val _subHead = atomic(0L)
var subHead: Long // guarded by subLock
get() = _subHead.value
Index: kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt (revision 4dd4e59f6676a45499cd193aa8fd9521013f8a3f)
+++ kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt (date 1605704376186)
@@ -7,7 +7,11 @@
import kotlinx.atomicfu.*
@Suppress("UNCHECKED_CAST")
-internal class CopyOnWriteList<E>() : AbstractMutableList<E>() {
+internal class CopyOnWriteList<E>() : AbstractMutableList<E>(), Dump {
+
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ seen.dumpField("array", _array.value)
+ }
private val _array = atomic<Array<Any?>>(arrayOfNulls<Any?>(0))
private var array: Array<Any?>
Index: kotlinx-coroutines-core/common/src/AbstractCoroutine.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- kotlinx-coroutines-core/common/src/AbstractCoroutine.kt (revision 4dd4e59f6676a45499cd193aa8fd9521013f8a3f)
+++ kotlinx-coroutines-core/common/src/AbstractCoroutine.kt (date 1605703019482)
@@ -7,6 +7,7 @@
import kotlinx.coroutines.CoroutineStart.*
import kotlinx.coroutines.intrinsics.*
+import kotlinx.coroutines.internal.*
import kotlin.coroutines.*
import kotlin.jvm.*
@@ -40,6 +41,11 @@
protected val parentContext: CoroutineContext,
active: Boolean = true
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ super.dumpImpl(seen)
+ seen.dumpField("parentContext", parentContext)
+ }
+
/**
* The context of this coroutine that includes this coroutine as a [Job].
*/
Index: kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt (revision 4dd4e59f6676a45499cd193aa8fd9521013f8a3f)
+++ kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt (date 1605704376111)
@@ -18,10 +18,14 @@
*/
internal abstract class AbstractSendChannel<E>(
@JvmField protected val onUndeliveredElement: OnUndeliveredElement<E>?
-) : SendChannel<E> {
+) : SendChannel<E>, Dump {
/** @suppress **This is unstable API and it is subject to change.** */
protected val queue = LockFreeLinkedListHead()
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ seen.dumpField("queue", queue)
+ }
+
// ------ extension points for buffered channels ------
/**
@@ -992,9 +996,16 @@
@JvmField val select: SelectInstance<R>,
block: suspend (Any?) -> R,
@JvmField val receiveMode: Int
- ) : Receive<E>(), DisposableHandle {
+ ) : Receive<E>(), DisposableHandle, Dump {
@JvmField val block: suspend (Any?) -> R = block.asShareable() // captured variables in this block need screening
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ super<Receive>.dumpImpl(seen)
+ seen.dumpField("channel", channel)
+ seen.dumpField("select", select)
+ seen.dumpField("block", block)
+ }
+
override fun tryResumeReceive(value: E, otherOp: PrepareOp?): Symbol? =
select.trySelectOther(otherOp) as Symbol?
@@ -1098,6 +1109,12 @@
private val _cont = atomic<CancellableContinuation<Unit>?>(cont)
protected val cont get() = _cont.value!!
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ super.dumpImpl(seen)
+ seen.dumpField("pollResult", pollResult)
+ seen.dumpField("cont", _cont)
+ }
+
override fun tryResumeSend(otherOp: PrepareOp?): Symbol? {
val token = _cont.value?.tryResume(Unit, otherOp?.desc) ?: return null
assert { token === RESUME_TOKEN } // the only other possible result
Index: kotlinx-coroutines-core/common/src/JobSupport.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- kotlinx-coroutines-core/common/src/JobSupport.kt (revision 4dd4e59f6676a45499cd193aa8fd9521013f8a3f)
+++ kotlinx-coroutines-core/common/src/JobSupport.kt (date 1605704376167)
@@ -26,7 +26,7 @@
* @suppress **This is unstable API and it is subject to change.**
*/
@Deprecated(level = DeprecationLevel.ERROR, message = "This is internal API and may be removed in the future releases")
-public open class JobSupport constructor(active: Boolean) : Job, ChildJob, ParentJob, SelectClause0 {
+public open class JobSupport constructor(active: Boolean) : Job, ChildJob, ParentJob, SelectClause0, Dump {
final override val key: CoroutineContext.Key<*> get() = Job
/*
@@ -134,6 +134,11 @@
get() = _parentHandle.value
set(value) { _parentHandle.value = value }
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ seen.dumpField("state", _state.value)
+ seen.dumpField("parentHandle", _parentHandle.value)
+ }
+
// ------------ initialization ------------
/**
@@ -1376,6 +1381,13 @@
override val list: NodeList? get() = null
override fun dispose() = (job as JobSupport).removeNode(this)
override fun toString() = "$classSimpleName@$hexAddress[job@${job.hexAddress}]"
+
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ super.dumpImpl(seen)
+ seen.dumpField("job", job)
+ seen.dumpField("isActive", isActive)
+ seen.dumpField("list", list)
+ }
}
internal class NodeList : LockFreeLinkedListHead(), Incomplete {
@@ -1491,6 +1503,12 @@
) : JobCancellingNode<JobSupport>(parent), ChildHandle {
override fun invoke(cause: Throwable?) = childJob.parentCancelled(job)
override fun childCancelled(cause: Throwable): Boolean = job.childCancelled(cause)
+ override fun toString(): String = "ChildHandle[$childJob]"
+
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ super.dumpImpl(seen)
+ seen.dumpField("childJob", childJob)
+ }
}
// Same as ChildHandleNode, but for cancellable continuation
Index: kotlinx-coroutines-core/nativeDarwin/test/Launcher.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- kotlinx-coroutines-core/nativeDarwin/test/Launcher.kt (revision 4dd4e59f6676a45499cd193aa8fd9521013f8a3f)
+++ kotlinx-coroutines-core/nativeDarwin/test/Launcher.kt (date 1605704376182)
@@ -4,8 +4,10 @@
package kotlinx.coroutines
+import kotlinx.coroutines.internal.*
import platform.CoreFoundation.*
import kotlin.native.concurrent.*
+import kotlin.native.internal.*
import kotlin.native.internal.test.*
import kotlin.system.*
@@ -26,5 +28,17 @@
testLauncherEntryPoint(args)
mainThread.shutdown()
DefaultDispatcher.shutdown()
+ printCycles()
+ }
+}
+
+private fun printCycles() {
+ GC.collect()
+ GC.detectCycles()?.let { cycles ->
+ println("!!! Detected ${cycles.size} cycles")
+ for (cycle in cycles) {
+ println("Cycle: $cycle")
+ if (cycle is FreezableAtomicReference<*>) cycle.value.dump(ArrayList())
+ }
}
}
\ No newline at end of file
Index: kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt (revision 4dd4e59f6676a45499cd193aa8fd9521013f8a3f)
+++ kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt (date 1605704376139)
@@ -26,11 +26,10 @@
internal open class CancellableContinuationImpl<in T>(
delegate: Continuation<T>,
resumeMode: Int
-) : DispatchedTask<T>(resumeMode), CancellableContinuation<T>, CoroutineStackFrame {
+) : DispatchedTask<T>(resumeMode), CancellableContinuation<T>, CoroutineStackFrame, Dump {
init {
assert { resumeMode != MODE_UNINITIALIZED } // invalid mode for CancellableContinuationImpl
}
-
@PublishedApi // for Kotlin/Native
final override val delegate: Continuation<T> = delegate.asShareable()
public override val context: CoroutineContext = delegate.context
@@ -79,6 +78,12 @@
get() = _parentHandle.value
set(value) { _parentHandle.value = value }
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ seen.dumpField("decision", _decision.value)
+ seen.dumpField("state", _state.value)
+ seen.dumpField("parentHandle", _parentHandle.value)
+ }
+
internal val state: Any? get() = _state.value
public override val isActive: Boolean get() = state is NotCompleted
@@ -558,7 +563,17 @@
@JvmField val onCancellation: ((cause: Throwable) -> Unit)? = null, // installed via resume block
@JvmField val idempotentResume: Any? = null,
@JvmField val cancelCause: Throwable? = null
-) {
+) : Dump {
+ override fun dumpImpl(seen: ArrayList<Any>) {
+ val p = seen.dumpPrefix()
+ println("$p CompletedContinuation@$hexAddress")
+ seen.dumpField("result", result)
+ seen.dumpField("cancelHandler", cancelHandler)
+ seen.dumpField("onCancellation", onCancellation)
+ seen.dumpField("idempotentResume", idempotentResume)
+ seen.dumpField("cancelCause", cancelCause)
+ }
+
val cancelled: Boolean get() = cancelCause != null
fun invokeHandlers(cont: CancellableContinuationImpl<*>, cause: Throwable) {
Index: kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt (revision 4dd4e59f6676a45499cd193aa8fd9521013f8a3f)
+++ kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt (date 1605704376120)
@@ -9,7 +9,7 @@
import kotlin.native.concurrent.*
/** @suppress **This is unstable API and it is subject to change.** */
-public expect open class LockFreeLinkedListNode() {
+public expect open class LockFreeLinkedListNode() : Dump {
public val isRemoved: Boolean
public val nextNode: LockFreeLinkedListNode
public val prevNode: LockFreeLinkedListNode
Index: kotlinx-coroutines-core/common/src/internal/Dump.kt
===================================================================
--- kotlinx-coroutines-core/common/src/internal/Dump.kt (date 1605704376118)
+++ kotlinx-coroutines-core/common/src/internal/Dump.kt (date 1605704376118)
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlin.coroutines.*
+
+
+public interface Dump {
+ public fun dump(seen: ArrayList<Any>) {
+ wrapDump(seen) { dumpImpl(seen) }
+ }
+
+ public fun dumpImpl(seen: ArrayList<Any>) {}
+}
+
+private inline fun Any.wrapDump(seen: ArrayList<Any>, block: () -> Unit) {
+ if (cycle(seen)) return
+ seen.add(this)
+ block()
+ seen.removeAt(seen.lastIndex)
+}
+
+public fun ArrayList<Any>.dumpPrefix(): String = " ".repeat(4 * size)
+
+public fun ArrayList<Any>.dumpField(name: String, value: Any?) {
+ println("${dumpPrefix()} $name: $value")
+ value.dump(this)
+}
+
+public fun Any?.dump(seen: ArrayList<Any>) {
+ when (this) {
+ is Dump -> dump(seen)
+ is Array<*> -> dump(seen)
+ is CoroutineContext -> dump(seen)
+ }
+}
+
+public fun Array<*>.dump(seen: ArrayList<Any>) {
+ wrapDump(seen) {
+ for (i in indices) seen.dumpField("[$i]", get(i))
+ }
+}
+
+public fun CoroutineContext.dump(seen: ArrayList<Any>) {
+ wrapDump(seen) {
+ fold(Unit) { _, element ->
+ if (element is Dump) element.dump(seen)
+ }
+ }
+}
+
+private fun Any.cycle(seen: ArrayList<Any>): Boolean {
+ for ((i, ref) in seen.withIndex()) {
+ if (ref === this) {
+ println("${seen.dumpPrefix()} !!! CYCLE to #$i: $ref")
+ return true
+ }
+ }
+ return false
+}
Index: kotlinx-coroutines-core/jvm/src/Builders.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- kotlinx-coroutines-core/jvm/src/Builders.kt (revision 4dd4e59f6676a45499cd193aa8fd9521013f8a3f)
+++ kotlinx-coroutines-core/jvm/src/Builders.kt (date 1605703482678)
@@ -8,6 +8,7 @@
package kotlinx.coroutines
+import kotlinx.coroutines.internal.*
import kotlinx.coroutines.intrinsics.*
import java.util.concurrent.locks.*
import kotlin.contracts.*
@@ -66,6 +67,12 @@
private val blockedThread: Thread,
private val eventLoop: EventLoop?
) : AbstractCoroutine<T>(parentContext, true) {
+ fun dumpImpl(seen: ArrayList<Any>) {
+ super.dumpImpl(seen)
+ seen.dumpField("blockedThread", blockedThread)
+ seen.dumpField("eventLoop", eventLoop)
+ }
+
override val isScopedCoroutine: Boolean get() = true
override fun afterCompletion(state: Any?) {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment