TracingDispatcher

A CoroutineDispatcher that wraps delegate and emits a TraceEvent.Queued, TraceEvent.Started and TraceEvent.Finished event to sink for every block dispatched through it. label identifies this dispatcher in the events.

Diagnostic tool, opt-in, not meant for production by default. When the sink is disabled for TraceLevel.Dispatch the original block is forwarded to the delegate untouched: no wrapper and no allocation. Coroutine name and thread identity are read only when the sink is enabled for TraceLevel.Coroutine. Every sink.emit is guarded: a faulty sink can never break dispatch or change the outcome of the block.

Every dispatched runnable gets a process-unique dispatchId, shared by its queued, started and finished events. When the dispatch context carries a TraceSpan, its id, parent id and name are attached to the three events; without one they are null. Reading it and allocating an id happen only when the sink is enabled.

Timestamps are nanoseconds on a monotonic base: the elapsed time since this dispatcher was created. Only differences between timestamps are meaningful.

What it observes, and what it does not. It observes dispatched segments, not all execution. A segment is the run of code between two suspension points that went through dispatch. Not visible:

  • Dispatchers.Unconfined, and any delegate whose isDispatchNeeded is false (for example Dispatchers.Main.immediate when already on the main thread): the block runs inline and never reaches dispatch.

  • the first segment of launch(start = CoroutineStart.UNDISPATCHED).

  • withContext to the same dispatcher, which continues inline.

  • the timer callback of Delay itself, which is not a dispatch (the resume that follows is).

Wrapping Dispatchers.Unconfined is pointless, and leaves a dangling TraceEvent.Queued on yield() because the delegate swallows the block. In general consumers must tolerate a TraceEvent.Queued without matching TraceEvent.Started and TraceEvent.Finished. A coroutine cancelled before its first run still emits Started and Finished for a segment that did no user work.

Wrapping also loses the identity of the delegate (the wrapper is not a MainCoroutineDispatcher) and its elasticity and limitedParallelism hints, as InstrumentedDispatcher documents. Calling limitedParallelism on the wrapper creates a view that is not traced separately.

It forwards Delay: when delegate has its own delay implementation (for example a TestDispatcher, whose virtual time must be honored), timers are scheduled on it; otherwise the standard default delay is used. Either way the coroutine resumes through this dispatcher, so the resume is observed.

Example:

val sink = RingBufferSink(256)
val io = TracingDispatcher(Dispatchers.ioCompat, "io", sink)
withContext(io + CoroutineName("fetch")) { api.fetch() }
sink.snapshot().forEach(::println) // Queued, Started, Finished

Thread-safety: stateless apart from the immutable creation timestamp; safe to share across coroutines and threads provided sink is thread-safe.

Requires opting in to ExperimentalStructuredCoroutines.

Parameters

delegate

dispatcher that actually runs the blocks.

label

name carried by every emitted TraceEvent.dispatcher.

sink

receiver of the events.

See also

Constructors

Link copied to clipboard
constructor(delegate: CoroutineDispatcher, label: String, sink: TraceSink)

Properties

Link copied to clipboard
open override val key: CoroutineContext.Key<*>
Link copied to clipboard

Functions

Link copied to clipboard
open suspend fun delay(time: Long)
Link copied to clipboard
open override fun dispatch(context: CoroutineContext, block: Runnable)
Link copied to clipboard
open override fun dispatchYield(context: CoroutineContext, block: Runnable)
Link copied to clipboard
open override fun <R> fold(initial: R, operation: (R, CoroutineContext.Element) -> R): R
Link copied to clipboard
open operator override fun <E : CoroutineContext.Element> get(key: CoroutineContext.Key<E>): E?
Link copied to clipboard
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
Link copied to clipboard
open override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle
Link copied to clipboard
open override fun isDispatchNeeded(context: CoroutineContext): Boolean
Link copied to clipboard
open fun limitedParallelism(parallelism: Int, name: String?): CoroutineDispatcher
Link copied to clipboard
open override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext
Link copied to clipboard
open operator fun plus(context: CoroutineContext): CoroutineContext
Link copied to clipboard
override fun releaseInterceptedContinuation(continuation: Continuation<*>)
Link copied to clipboard
open override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>)
Link copied to clipboard
open override fun toString(): String