Give each thread its own local label while the main thread combines stable results.

thread local `ThreadLocal` stores one value per thread, which keeps per-thread context separate even when the same function runs twice.

Thread Local Context

suffix
ThreadLocalContext.kt
Replay: real traced execution (multi-file project)
val currentLabel = ThreadLocal.withInitial { "unset" }

fun collectLabel(label: String, slot: Int, output: Array<String>) {
    currentLabel.set(label)
    val local = currentLabel.get()
    output[slot] = "$local:${label.length}"
    currentLabel.remove()
}

fun main() {
    val suffix = "api"
    val output = Array(2) { "" }

    val first = Thread { collectLabel("read-$suffix", 0, output) }
    val second = Thread { collectLabel("write-$suffix", 1, output) }

    first.start()
    second.start()
    first.join()
    second.join()

    println("suffix=$suffix")
    println(output.joinToString("|"))
}
val currentLabel = ThreadLocal.withInitial { "unset" }

fun collectLabel(label: String, slot: Int, output: Array<String>) {
    currentLabel.set(label)
    val local = currentLabel.get()
    output[slot] = "$local:${label.length}"
    currentLabel.remove()
}

fun main() {
    val suffix = "ui"
    val output = Array(2) { "" }

    val first = Thread { collectLabel("read-$suffix", 0, output) }
    val second = Thread { collectLabel("write-$suffix", 1, output) }

    first.start()
    second.start()
    first.join()
    second.join()

    println("suffix=$suffix")
    println(output.joinToString("|"))
}
val currentLabel = ThreadLocal.withInitial { "unset" }

fun collectLabel(label: String, slot: Int, output: Array<String>) {
    currentLabel.set(label)
    val local = currentLabel.get()
    output[slot] = "$local:${label.length}"
    currentLabel.remove()
}

fun main() {
    val suffix = "jobs"
    val output = Array(2) { "" }

    val first = Thread { collectLabel("read-$suffix", 0, output) }
    val second = Thread { collectLabel("write-$suffix", 1, output) }

    first.start()
    second.start()
    first.join()
    second.join()

    println("suffix=$suffix")
    println(output.joinToString("|"))
}
  1. suffix ← api, first ← Thread[#19,Thread-0,5,main], second ← Thread[#20,Thread-1,5,main]

    10fun main() {11    val suffix→ api = "api" //@suffix="ui", "jobs"12    val output = Array(2) { "" }1314    val first→ Thread[#19,Thread-0,5,main] = Thread { collectLabel("read-$suffix", 0, output) }15    val second→ Thread[#20,Thread-1,5,main] = Thread { collectLabel("write-$suffix", 1, output) }1617    firstThread[#19,Thread-0,5,main].start()18    secondThread[#20,Thread-1,5,main].start()19    firstThread[#19,Thread-0,5,main].join()20    second.join()
  2. fun collectLabel(label: String, slot: Int, output: Array<String>)

    pass 1 of 2
    3fun collectLabel(labelread-api: String, slot0: Int, output: Array<String>) {4    currentLabel.set(label)
  3. currentLabel ← ⟨ThreadLocal$SuppliedThreadLocal A⟩, label ← read-api

    pass 2 of 2
    3fun collectLabel(labelwrite-api: String, slot1: Int, output: Array<String>) {4    currentLabel→ ⟨ThreadLocal$SuppliedThreadLocal A⟩.set(label→ read-api)5    val local→ write-api = currentLabel→ ⟨ThreadLocal$SuppliedThreadLocal A⟩.get()6    output[slot]→ write-api:9 = "$local→ write-api:${label.length→ 9}"7    currentLabel⟨ThreadLocal$SuppliedThreadLocal A⟩.remove()8}
  4. first ← Thread[#19,Thread-0,5,]

    18    second.start()19    first→ Thread[#19,Thread-0,5,].join()20    secondThread[#20,Thread-1,5,].join()2122    println("suffix=$suffixapi")23    println(output.joinToString("|"))24}
    outputsuffix=api
    read-api:8|write-api:9
  1. suffix ← ui, first ← Thread[#19,Thread-0,5,main], second ← Thread[#20,Thread-1,5,main]

    10fun main() {11    val suffix→ ui = "ui"12    val output = Array(2) { "" }1314    val first→ Thread[#19,Thread-0,5,main] = Thread { collectLabel("read-$suffix", 0, output) }15    val second→ Thread[#20,Thread-1,5,main] = Thread { collectLabel("write-$suffix", 1, output) }1617    firstThread[#19,Thread-0,5,main].start()18    secondThread[#20,Thread-1,5,main].start()19    first.join()
  2. fun collectLabel(label: String, slot: Int, output: Array<String>)

    pass 1 of 2
    3fun collectLabel(labelread-ui: String, slot0: Int, output: Array<String>) {4    currentLabel.set(label)
  3. fun collectLabel(label: String, slot: Int, output: Array<String>)

    pass 2 of 2
    3fun collectLabel(labelwrite-ui: String, slot1: Int, output: Array<String>) {4    currentLabel⟨ThreadLocal$SuppliedThreadLocal A⟩.set(labelread-ui)5    val local = currentLabel.get()
  4. first ← Thread[#19,Thread-0,5,], currentLabel ← ⟨ThreadLocal$SuppliedThreadLocal A⟩

    3fun collectLabel(label: String, slot: Int, output: Array<String>) {4    currentLabel→ ⟨ThreadLocal$SuppliedThreadLocal A⟩.set(label→ write-ui)5    val local→ write-ui = currentLabel→ ⟨ThreadLocal$SuppliedThreadLocal A⟩.get()6    output[slot]→ read-ui:7 = "$local→ read-ui:${label.length→ 7}"7    currentLabel⟨ThreadLocal$SuppliedThreadLocal A⟩.remove()8}910fun main() {11    val suffix = "ui"12    val output = Array(2) { "" }1314    val first = Thread { collectLabel("read-$suffix", 0, output) }15    val second = Thread { collectLabel("write-$suffix", 1, output) }1617    first.start()18    second.start()19    first→ Thread[#19,Thread-0,5,].join()20    secondThread[#20,Thread-1,5,].join()2122    println("suffix=$suffixui")23    println(output.joinToString("|"))24}
    outputsuffix=ui
    read-ui:7|write-ui:8
    values this step0slot
  1. suffix ← jobs, first ← Thread[#19,Thread-0,5,main], second ← Thread[#20,Thread-1,5,main]

    10fun main() {11    val suffix→ jobs = "jobs"12    val output = Array(2) { "" }1314    val first→ Thread[#19,Thread-0,5,main] = Thread { collectLabel("read-$suffix", 0, output) }15    val second→ Thread[#20,Thread-1,5,main] = Thread { collectLabel("write-$suffix", 1, output) }1617    firstThread[#19,Thread-0,5,main].start()18    secondThread[#20,Thread-1,5,main].start()19    first.join()
  2. fun collectLabel(label: String, slot: Int, output: Array<String>)

    pass 1 of 2
    3fun collectLabel(labelread-jobs: String, slot0: Int, output: Array<String>) {4    currentLabel⟨ThreadLocal$SuppliedThreadLocal A⟩.set(labelread-jobs)5    val local = currentLabel.get()
  3. second.start()

    17first.start()18secondThread[#20,Thread-1,5,main].start()19firstThread[#19,Thread-0,5,main].join()20second.join()
  4. currentLabel ← ⟨ThreadLocal$SuppliedThreadLocal A⟩, local ← read-jobs

    pass 2 of 2
    3fun collectLabel(labelwrite-jobs: String, slot1: Int, output: Array<String>) {4    currentLabel⟨ThreadLocal$SuppliedThreadLocal A⟩.set(labelwrite-jobs)5    val local→ read-jobs = currentLabel→ ⟨ThreadLocal$SuppliedThreadLocal A⟩.get()6    output[slot]→ write-jobs:10 = "$local→ write-jobs:${label.length→ 10}"7    currentLabel⟨ThreadLocal$SuppliedThreadLocal A⟩.remove()8}
  5. first ← Thread[#19,Thread-0,5,]

    18    second.start()19    first→ Thread[#19,Thread-0,5,].join()20    secondThread[#20,Thread-1,5,].join()2122    println("suffix=$suffixjobs")23    println(output.joinToString("|"))24}
    outputsuffix=jobs
    read-jobs:9|write-jobs:10