Skip to content

Commit e2cc5c4

Browse files
committed
Fix VirtualTimeSource not noticing that DefaultDelay is about to get created
1 parent 0bc0d7a commit e2cc5c4

File tree

3 files changed

+14
-2
lines changed

3 files changed

+14
-2
lines changed

Diff for: kotlinx-coroutines-core/jvm/src/AbstractTimeSource.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ internal inline fun unTrackTask(obj: Any) {
9999

100100
/**
101101
* Increases the registered number of nested loops of the form
102-
* `while (nanoTime() < deadline) { parkNanos(deadline - nanoTime()) }` running in the current thread.
102+
* `while (nanoTime() < deadline) { parkNanos(deadline - nanoTime()) }` corresponding to the object [key]
103+
* and signals that the current thread is in such a loop.
103104
*
104105
* While at least one such loop is running, other threads are allowed to call [unpark] on the current thread
105106
* and wake it up. Before [registerTimeLoopThread] is called, [unpark] is not guaranteed to have any effect.

Diff for: kotlinx-coroutines-core/jvm/src/DefaultDelay.kt

+12
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ private object DefaultDelayImpl : EventLoopImplBase(), Runnable {
7575
try {
7676
ThreadLocalEventLoop.setEventLoop(DelegatingUnconfinedEventLoop)
7777
registerTimeLoopThread()
78+
unTrackTask(this) /** see the comment in [startThreadOrObtainSleepingThread] */
7879
try {
7980
while (true) {
8081
Thread.interrupted() // just reset interruption flag
@@ -107,6 +108,17 @@ private object DefaultDelayImpl : EventLoopImplBase(), Runnable {
107108
This means that whatever thread is going to be running by the end of this function,
108109
it's going to notice the tasks it's supposed to run.
109110
We can return `null` unconditionally. */
111+
/** If this function is called from a thread that's already registered as a time loop thread,
112+
because a time loop thread is not parked right now, the time source will not advance time *currently*,
113+
but it may do that as soon as the thread calling this is parked, which may happen earlier than the default
114+
delay thread has a chance to run.
115+
Because of that, we notify the time source that something is actually happening right now.
116+
This would work automatically if instead of [scheduleBackgroundIoTask] we used [CoroutineDispatcher.dispatch] on
117+
[Dispatchers.IO], but then, none of the delays would be skipped, as the whole time a [DefaultDelay] thread runs
118+
would be considered as a task.
119+
Therefore, we register a task right now and mark it as completed as soon as a [DefaultDelay] time loop gets
120+
registered. */
121+
trackTask(this)
110122
scheduleBackgroundIoTask(this)
111123
return null
112124
}

Diff for: kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt

-1
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ internal class VirtualTimeSource(
144144
isShutdown = true
145145
wakeupAll()
146146
while (!threads.isEmpty()) (this as Object).wait()
147-
assert(trackedTasks.isEmpty()) { "There are still tracked tasks: $trackedTasks" }
148147
}
149148

150149
private fun wakeupAll() {

0 commit comments

Comments
 (0)