Skip to content

Coroutine on EventLoop dispatcher fails to yield to a task which is resuming after a delay #4134

Closed
@roomscape

Description

@roomscape
fun main() = runBlocking {
  launch {
    delay(1)
    println("This should be first")
  }
  Thread.sleep(1000)
  yield()
  println("This should be second")
}

Actual behaviour

The code outputs:

This should be second
This should be first

The call to yield() resumes immediately, instead of yielding control to the other coroutine.

This seems to be an issue with the EventLoop dispatcher used by runBlocking. It maintains a separate queue for delayed continuations. On each iteration of the event loop, tasks whose delay has expired are moved from the delay queue to the main event queue. During a call to yield(), this takes place after the yield() function has added its own continuation to the event queue. That means a yield() call will always fail to yield if the only other continuations waiting to resume are ones whose delay has expired since the last dispatch.

Expected behaviour

Output should be:

This should be first
This should be second

On reaching the call to yield(), the launch job is already eligible to resume. 1 second has elapsed, which is much longer than the 1ms required delay. The dispatcher should prefer to dispatch the launch job, instead of resuming the same coroutine that called yield().

Environment

Reproduced using Kotlin 1.9 with coroutines 1.8.1, on JVM 1.8.

This issue was first reported by a user on Slack: https://kotlinlang.slack.com/archives/C0B8MA7FA/p1716495410746749

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions