@@ -24,13 +24,16 @@ import java.util.concurrent.atomic.AtomicLong
2424 * under the **same** [callKey], so downstream observers see the latest snapshot and
2525 * concurrent calls — even ones sharing a trace id — never collide (see [CallContext]).
2626 *
27- * The default [callKey] is derived from the trace and span ids (`traceId:spanId`), which is
28- * adequate when the span id is genuinely per-call. Untraced calls share constant no-op ids,
29- * so [default] mints a process-unique key instead; supply an explicit [callKey] to override.
27+ * The default [callKey] appends a process-unique counter to the trace/span derivation
28+ * (`traceId:spanId:n`), so it is call-unique even when the span id is not. The trace and span
29+ * ids alone are not a safe key: the no-op context shares constant ids across untraced calls,
30+ * an inbound W3C trace shares a trace id across spans, and a tracer may reuse a span id across
31+ * sibling calls — all of which would otherwise collide in [ContextStore]. Supply an explicit
32+ * [callKey] to override (e.g. to re-key onto an existing chain).
3033 */
3134public data class DispatchContext (
3235 override val instrumentationContext : InstrumentationContext ,
33- override val callKey : String = deriveCallKey (instrumentationContext),
36+ override val callKey : String = mintCallKey (instrumentationContext),
3437) : CallContext {
3538 /* *
3639 * Promotes this dispatch context into a [RequestContext] bound to [request] and stores
@@ -51,25 +54,29 @@ public data class DispatchContext(
5154 private val mintCounter: AtomicLong = AtomicLong ()
5255
5356 /* *
54- * Derives the default store key for [instrumentationContext] from its trace and span
55- * ids (`traceId:spanId`). Unique per call only when the span id is per-call; the
56- * no-op context shares constant ids, so [default] does not use this derivation.
57+ * Derives the trace/span portion of a store key for [instrumentationContext]
58+ * (`traceId:spanId`). This portion is not call-unique on its own — the no-op context
59+ * shares constant ids, an inbound trace shares a trace id across spans, and a span id
60+ * may be reused across sibling calls — so [mintCallKey] appends a process-unique
61+ * counter to it for the actual key. Retained as the fallback derivation that
62+ * [RequestContext] and [ExchangeContext] use when constructed directly.
5763 */
5864 internal fun deriveCallKey (instrumentationContext : InstrumentationContext ): String =
5965 instrumentationContext.traceId.value + " :" + instrumentationContext.spanId.value
6066
6167 /* *
6268 * A dispatch context with a no-op instrumentation context; used when tracing is
63- * disabled. Mints a process-unique [callKey] because the no-op context's trace and
64- * span ids are shared constants — two untraced calls would otherwise collide in
65- * [ContextStore].
69+ * disabled. The primary constructor's default [callKey] already mints a process-unique
70+ * key, so this just constructs one with the no-op context.
6671 */
67- public fun default (): DispatchContext =
68- DispatchContext (
69- instrumentationContext = NoopInstrumentationContext ,
70- callKey = mintCallKey(NoopInstrumentationContext ),
71- )
72+ public fun default (): DispatchContext = DispatchContext (NoopInstrumentationContext )
7273
74+ /* *
75+ * Mints a call-unique store key by appending a monotonically increasing,
76+ * process-unique counter to [deriveCallKey]'s trace/span derivation
77+ * (`traceId:spanId:n`). The counter disambiguates calls that would otherwise share a
78+ * trace/span pair, so distinct calls never collide in [ContextStore].
79+ */
7380 private fun mintCallKey (instrumentationContext : InstrumentationContext ): String =
7481 deriveCallKey(instrumentationContext) + " :" + mintCounter.incrementAndGet()
7582 }
0 commit comments