question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Channel possibly not acting fair

See original GitHub issue

I noticed a few scenarios with channels where they do not seem to be fair and are filling their buffer in the wrong order. The main gist of the post can be seen from the first example, while the additional examples provide alternate scenarios with more unexpected outputs.

suspend fun sendString(channel: SendChannel<String>, s: String) {
    while (true) {
        channel.send(s)
    }
}

fun main(args: Array<String>) = runBlocking<Unit> {
    val channel = Channel<String>()
    launch(coroutineContext) { sendString(channel, "foo") }
    launch(coroutineContext) { sendString(channel, "BAR!") }
    repeat(6) { // receive first six
        println(channel.receive())
    }
}

Outputs:

foo
foo
BAR!
foo
foo
BAR!

If the channel is given a buffer, the buffer is then filled with as many foo’s as it can hold, and then a single BAR! which causes an output of the following when the channel has a buffer of 4:

foo
foo
foo
foo
foo
foo

The buffer is always filled to its max capacity with “foo” and only gets a “BAR!” afterwards at which point the send(“BAR!”) is suspended until the multiple foo’s in the buffer are drained.

Filling the buffer like this also has some other weird results when a delay is added either before or after channel.receive() is called, as seen below:

fun main(args: Array<String>) = runBlocking<Unit> {
    val channel = Channel<String>(2)
    launch(coroutineContext) { sendString(channel, "foo") }
    launch(coroutineContext) { sendString(channel, "BAR!") }
    repeat(6) { // receive first six
        delay(1L)
        println(channel.receive())
    }
}

Outputs:

foo
BAR!
foo
BAR!
foo
BAR!

Above is what I anticipated the original code to output since that seems more like the channel is acting fair.

fun main(args: Array<String>) = runBlocking<Unit> {
    val channel = Channel<String>()
    launch(coroutineContext) { sendString(channel, "foo") }
    launch(coroutineContext) { sendString(channel, "BAR!") }
    repeat(6) { // receive first six
        println(channel.receive())
        delay(1L)
    }
}

Outputs:

foo
foo
BAR!
foo
BAR!
foo

From the code above and through some debugging, send(“foo”) is getting called, then it is getting to send(“foo”) a second time at which point it suspends, then it goes to send(“BAR!”) and suspends which causes the next strings in the buffer to be ordered such that “foo” is before “BAR!” for the first two items emitted.

And the last scenario:

fun main(args: Array<String>) = runBlocking<Unit> {
    val channel = Channel<String>()
    launch(coroutineContext) { sendString(channel, "foo") }
    launch(coroutineContext) { sendString(channel, "BAR!") }
    delay(1L)
    repeat(6) { // receive first six
        println(channel.receive())
    }
}

Outputs:

foo
BAR!
foo
foo
BAR!
foo

This case was the weirdest as the execution of the while loop and the suspensions from send in sendString were in a very weird order that caused two “foo”'s to output in position 2 and 3.

Sorry for the long post, I noticed this while playing around with coroutines and did not think it was intended so I thought I would post here. If this is intended or there is a silly I’m making, any explanation is appreciated. Thanks.

Issue Analytics

  • State:open
  • Created 6 years ago
  • Reactions:15
  • Comments:13 (9 by maintainers)

github_iconTop GitHub Comments

48reactions
elizarovcommented, Sep 7, 2017

This is a side effect of the fact that all the coroutines run under the same context in the same thread. Let me provide a step by step explanation of the first scenario (with unbuffered rendezvous channel):

  1. From the “main” coroutine we schedule two coroutines to run using launch (let’s call them “foo” coroutine and “bar” coroutine).
  2. The “main” coroutine attempts to receive from the channel, there is no sender, so receive function suspends (freeing the execution thread for the next coroutine in queue). Now the execution queue looks like this: “foo”, “bar”. Suspended “main” waits on receive and is not scheduled for execution.
  3. The “foo” coroutine is executed, it sends “foo”, which rendezvous with the suspended “main” coroutine and resumes it. Now the execution queue looks like this: “foo”, “bar”, “main” (to receive “foo”)
  4. The “foo” coroutine continues to execute (it was not suspended, but successfully sent) and attempts to send again. This time there is no one the receive side, no rendezvous, and it gets suspended. Execution queue: “bar”, “main” (to receive “foo”). Suspended “foo” waits on send.
  5. The “bar” coroutine is executed. It attempts to send “bar”, but there is no rendezvous with receiver, so it suspends, too. Execution queue: “main” (to receive “foo”). Suspended “for” and “bar” wait on send (in this order)
  6. The “main” coroutine is executing, resuming from receive with a value of “foo”. It prints “foo”, then does another receive.
  7. Now there is a queue of both “for” and “bar” waiting on send, so the “main” coroutine will successfully rendezvous with them printing, one more “foo” and printing “bar” and resuming the corresponding coroutines. Execution queue: “main”, “foo”, “bar”. We are still executing “main”.
  8. The next attempt to receive by the “main” coroutine does not rendezvous and suspends. Execution queue: “foo”, “bar”. Suspended “main” waits on receive.
  9. You can see that now we are in a similar state that was after step 2, so you can continue from step 3.

The resulting “foo”, “foo”, "bar, “foo”, “foo”, “bar”, etc does look counter-intivive, though. I’ll keep this issue open for two reasons:

  • Think if something can be adjusted in channel rendezvous or execution logic so that outcome becomes more “fair”.
  • Or document and explain this behavior better.
3reactions
elizarovcommented, Jun 18, 2019

No update so far. We’ve been thinking about fairness of the channels, though, and here are some thoughts:

  • Making channels “always fair” adds visible overhead. That is not something we’d want to do.
  • Leaving channels “unfair” as they are now creates various obviously problematic case (especially notable is the one contributed by @fvasco)

So, the current thinking that that we should make channels “partially fair” – yield periodically (say on every 32 sends of something like that). It is not easy to add to the current design of channel, but as we work on a different channel implementation that feature can be quite easily incorporated at little additional cost.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Final Rule: Selective Disclosure and Insider Trading
The regulation provides that when an issuer, or person acting on its behalf, discloses material nonpublic information to certain enumerated persons (in general ......
Read more >
Copyright Fair Use | Media Law 101
Fair use is a legal doctrine that permits the unlicensed use of such works in limited circumstances. The doctrine is based on the...
Read more >
The Public and Broadcasting
We do not license TV or radio networks (such as CBS, NBC, ABC or Fox) or other organizations that stations have relationships with,...
Read more >
Fair Dealing Guidelines
These guidelines are intended as a guide only and legal advice from your programme lawyer must be sought at an early stage.
Read more >
Regulation FD: Fair Disclosure
reckless in not knowing, is both material and nonpublic.” Materiality: Determining whether information is mate- rial is perhaps the most difficult aspect of ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found