2

Given that there exists a way for notating sequences and mixing imperative code in Kotlin, is the required code within the lambda which is argument to the sequence function required to be thread-safe?

For example, is the following safe:

var x: Int = 5

fun someSequence(): Sequence<Int> = sequence {
    while (true) {
        x++
        yield(x)
    }
}

fun main(args: Array<String>) {
    val seq = someSequence()
    seq.take(200).forEach(::println)
}

Because there is no inherent parallelism to exploit when constructing sequences, I do not expect trouble with the order of operations. However, given that sequence is implemented with help of a coroutine:

public fun <T> sequence(@BuilderInference block: suspend SequenceScope<T>.() -> Unit): Sequence<T> = Sequence { iterator(block) }

public fun <T> iterator(@BuilderInference block: suspend SequenceScope<T>.() -> Unit): Iterator<T> {
    val iterator = SequenceBuilderIterator<T>()
    iterator.nextStep = block.createCoroutineUnintercepted(receiver = iterator, completion = iterator)
    return iterator
}

and coroutines are not fixed to a specific thread in general, I fear cached reads. I imagine two alternative scenarios:

  1. The sequence function takes special care, such that the lambda which generates the next element is always executed in the same thread. Coroutines / suspend functions are an implementation detail that temporarily transfers control flow to the consumer of the sequence. Is this what @RestrictSuspension is about? (From Is this implementation of takeWhileInclusive safe?)

  2. The lambda passed to sequence has to be thread-safe. Why is the documentation so tacit about this? Also tutorials only cover very simple use-cases.

Please elaborate which is case, and why.

f4lco
  • 3,728
  • 5
  • 28
  • 53

1 Answers1

2

The sequence's coroutine executes on the calling thread, so all the thread safety concerns are the caller's responsibility.

In general, if you pass the sequence to other threads so that the coroutine resumes on another thread each time, all you need to ensure is that there is a happens-before relationship established from the suspension to the resumption, providing the overall effect as if the coroutine has been executing sequentially on a single thread.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • For my use-case, the calling thread will always be the main thread. This implies that there is no [dispatcher](https://kotlinlang.org/docs/reference/coroutines/coroutine-context-and-dispatchers.html) in between? Is there really a fully-fledged coroutine, or is `SequenceScope` just using parts of the coroutine machinery? I still marvel at how a coroutine implements construction of a sequential data structure. – f4lco Mar 29 '19 at 12:43
  • 1
    No, you don't need a dispatcher to run coroutines, it is a layer on top of them. And sequences never use them. You can check out [this answer](https://stackoverflow.com/questions/47871868/what-does-suspend-function-mean-in-kotlin-coroutine/48112934#48112934) that shows you the mechanics of suspending and resuming a coroutine with your own code. You can easily extrapolate from that code to the code that would generate another member of a sequence each time it is resumed. – Marko Topolnik Mar 29 '19 at 13:18
  • That linked answer of yours is very enlightening, thanks. I see now how a dispatchers and a coroutines are not necessarily coupled (exactly answer's last paragraph), and can relate to the sequence topic well. Solved :) – f4lco Mar 29 '19 at 21:21