Skip to content

Commit 96a5c8e

Browse files
committed
Optimize virtual time source so that DefaultTimeSource is not needed
* Default implementation is "null" reference to time-source which avoids the need for interface call in default case.
1 parent fa9104e commit 96a5c8e

13 files changed

+82
-52
lines changed

knit/src/Knit.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
import java.io.*
@@ -228,7 +228,7 @@ fun knit(markdownFile: File): Boolean {
228228
}
229229
}
230230
for (code in codeLines) {
231-
outLines += code.replace("System.currentTimeMillis()", "timeSource.currentTimeMillis()")
231+
outLines += code.replace("System.currentTimeMillis()", "currentTimeMillis()")
232232
}
233233
codeLines.clear()
234234
writeLinesIfNeeded(file, outLines)

kotlinx-coroutines-core/jvm/src/Builders.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ private class BlockingCoroutine<T>(
6969

7070
@Suppress("UNCHECKED_CAST")
7171
fun joinBlocking(): T {
72-
timeSource.registerTimeLoopThread()
72+
registerTimeLoopThread()
7373
try {
7474
eventLoop?.incrementUseCount()
7575
try {
@@ -79,13 +79,13 @@ private class BlockingCoroutine<T>(
7979
val parkNanos = eventLoop?.processNextEvent() ?: Long.MAX_VALUE
8080
// note: process next even may loose unpark flag, so check if completed before parking
8181
if (isCompleted) break
82-
timeSource.parkNanos(this, parkNanos)
82+
parkNanos(this, parkNanos)
8383
}
8484
} finally { // paranoia
8585
eventLoop?.decrementUseCount()
8686
}
8787
} finally { // paranoia
88-
timeSource.unregisterTimeLoopThread()
88+
unregisterTimeLoopThread()
8989
}
9090
// now return result
9191
val state = this.state.unboxState()

kotlinx-coroutines-core/jvm/src/CommonPool.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ internal object CommonPool : ExecutorCoroutineDispatcher() {
100100

101101
override fun dispatch(context: CoroutineContext, block: Runnable) {
102102
try {
103-
(pool ?: getOrCreatePoolSync()).execute(timeSource.wrapTask(block))
103+
(pool ?: getOrCreatePoolSync()).execute(wrapTask(block))
104104
} catch (e: RejectedExecutionException) {
105-
timeSource.unTrackTask()
105+
unTrackTask()
106106
DefaultExecutor.enqueue(block)
107107
}
108108
}

kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt

+5-5
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ internal object DefaultExecutor : EventLoopImplBase(), Runnable {
5959

6060
override fun run() {
6161
ThreadLocalEventLoop.setEventLoop(this)
62-
timeSource.registerTimeLoopThread()
62+
registerTimeLoopThread()
6363
try {
6464
var shutdownNanos = Long.MAX_VALUE
6565
if (!notifyStartup()) return
@@ -69,7 +69,7 @@ internal object DefaultExecutor : EventLoopImplBase(), Runnable {
6969
if (parkNanos == Long.MAX_VALUE) {
7070
// nothing to do, initialize shutdown timeout
7171
if (shutdownNanos == Long.MAX_VALUE) {
72-
val now = timeSource.nanoTime()
72+
val now = nanoTime()
7373
if (shutdownNanos == Long.MAX_VALUE) shutdownNanos = now + KEEP_ALIVE_NANOS
7474
val tillShutdown = shutdownNanos - now
7575
if (tillShutdown <= 0) return // shut thread down
@@ -80,13 +80,13 @@ internal object DefaultExecutor : EventLoopImplBase(), Runnable {
8080
if (parkNanos > 0) {
8181
// check if shutdown was requested and bail out in this case
8282
if (isShutdownRequested) return
83-
timeSource.parkNanos(this, parkNanos)
83+
parkNanos(this, parkNanos)
8484
}
8585
}
8686
} finally {
8787
_thread = null // this thread is dead
8888
acknowledgeShutdownIfNeeded()
89-
timeSource.unregisterTimeLoopThread()
89+
unregisterTimeLoopThread()
9090
// recheck if queues are empty after _thread reference was set to null (!!!)
9191
if (!isEmpty) thread // recreate thread if it is needed
9292
}
@@ -126,7 +126,7 @@ internal object DefaultExecutor : EventLoopImplBase(), Runnable {
126126
if (!isShutdownRequested) debugStatus = SHUTDOWN_REQ
127127
// loop while there is anything to do immediately or deadline passes
128128
while (debugStatus != SHUTDOWN_ACK && _thread != null) {
129-
_thread?.let { timeSource.unpark(it) } // wake up thread if present
129+
_thread?.let { unpark(it) } // wake up thread if present
130130
val remaining = deadline - System.currentTimeMillis()
131131
if (remaining <= 0) break
132132
(this as Object).wait(timeout)

kotlinx-coroutines-core/jvm/src/EventLoop.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,13 @@ internal abstract class EventLoopImplBase: EventLoop(), Delay {
6868
}
6969
val delayed = _delayed.value ?: return Long.MAX_VALUE
7070
val nextDelayedTask = delayed.peek() ?: return Long.MAX_VALUE
71-
return (nextDelayedTask.nanoTime - timeSource.nanoTime()).coerceAtLeast(0)
71+
return (nextDelayedTask.nanoTime - nanoTime()).coerceAtLeast(0)
7272
}
7373

7474
private fun unpark() {
7575
val thread = thread
7676
if (Thread.currentThread() !== thread)
77-
timeSource.unpark(thread)
77+
unpark(thread)
7878
}
7979

8080
override fun shutdown() {
@@ -99,7 +99,7 @@ internal abstract class EventLoopImplBase: EventLoop(), Delay {
9999
// queue all delayed tasks that are due to be executed
100100
val delayed = _delayed.value
101101
if (delayed != null && !delayed.isEmpty) {
102-
val now = timeSource.nanoTime()
102+
val now = nanoTime()
103103
while (true) {
104104
// make sure that moving from delayed to queue removes from delayed only after it is added to queue
105105
// to make sure that 'isEmpty' and `nextTime` that check both of them
@@ -251,7 +251,7 @@ internal abstract class EventLoopImplBase: EventLoop(), Delay {
251251

252252
override var index: Int = -1
253253

254-
@JvmField val nanoTime: Long = timeSource.nanoTime() + delayToNanos(timeMillis)
254+
@JvmField val nanoTime: Long = nanoTime() + delayToNanos(timeMillis)
255255

256256
override fun compareTo(other: DelayedTask): Int {
257257
val dTime = nanoTime - other.nanoTime

kotlinx-coroutines-core/jvm/src/Executors.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ internal abstract class ExecutorCoroutineDispatcherBase : ExecutorCoroutineDispa
6161

6262
override fun dispatch(context: CoroutineContext, block: Runnable) {
6363
try {
64-
executor.execute(timeSource.wrapTask(block))
64+
executor.execute(wrapTask(block))
6565
} catch (e: RejectedExecutionException) {
66-
timeSource.unTrackTask()
66+
unTrackTask()
6767
DefaultExecutor.enqueue(block)
6868
}
6969
}
+49-19
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
/*
2-
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

5+
// Need InlineOnly for efficient bytecode on Android
6+
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "NOTHING_TO_INLINE")
7+
58
package kotlinx.coroutines
69

7-
import java.util.concurrent.locks.LockSupport
10+
import java.util.concurrent.locks.*
11+
import kotlin.internal.InlineOnly
812

913
internal interface TimeSource {
1014
fun currentTimeMillis(): Long
@@ -18,22 +22,48 @@ internal interface TimeSource {
1822
fun unpark(thread: Thread)
1923
}
2024

21-
internal object DefaultTimeSource : TimeSource {
22-
override fun currentTimeMillis(): Long = System.currentTimeMillis()
23-
override fun nanoTime(): Long = System.nanoTime()
24-
override fun wrapTask(block: Runnable): Runnable = block
25-
override fun trackTask() {}
26-
override fun unTrackTask() {}
27-
override fun registerTimeLoopThread() {}
28-
override fun unregisterTimeLoopThread() {}
29-
30-
override fun parkNanos(blocker: Any, nanos: Long) {
31-
LockSupport.parkNanos(blocker, nanos)
32-
}
33-
34-
override fun unpark(thread: Thread) {
35-
LockSupport.unpark(thread)
36-
}
25+
// For tests only
26+
// @JvmField: Don't use JvmField here to enable R8 optimizations via "assumenosideeffects"
27+
internal var timeSource: TimeSource? = null
28+
29+
@InlineOnly
30+
internal inline fun currentTimeMillis(): Long =
31+
timeSource?.currentTimeMillis() ?: System.currentTimeMillis()
32+
33+
@InlineOnly
34+
internal inline fun nanoTime(): Long =
35+
timeSource?.nanoTime() ?: System.nanoTime()
36+
37+
@InlineOnly
38+
internal inline fun wrapTask(block: Runnable): Runnable =
39+
timeSource?.wrapTask(block) ?: block
40+
41+
@InlineOnly
42+
internal inline fun trackTask() {
43+
timeSource?.trackTask()
44+
}
45+
46+
@InlineOnly
47+
internal inline fun unTrackTask() {
48+
timeSource?.unTrackTask()
3749
}
3850

39-
internal var timeSource: TimeSource = DefaultTimeSource
51+
@InlineOnly
52+
internal inline fun registerTimeLoopThread() {
53+
timeSource?.registerTimeLoopThread()
54+
}
55+
56+
@InlineOnly
57+
internal inline fun unregisterTimeLoopThread() {
58+
timeSource?.unregisterTimeLoopThread()
59+
}
60+
61+
@InlineOnly
62+
internal inline fun parkNanos(blocker: Any, nanos: Long) {
63+
timeSource?.parkNanos(blocker, nanos) ?: LockSupport.parkNanos(blocker, nanos)
64+
}
65+
66+
@InlineOnly
67+
internal inline fun unpark(thread: Thread) {
68+
timeSource?.unpark(thread) ?: LockSupport.unpark(thread)
69+
}

kotlinx-coroutines-core/jvm/src/channels/TickerChannels.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package kotlinx.coroutines.channels
@@ -80,13 +80,13 @@ private suspend fun fixedPeriodTicker(
8080
initialDelayMillis: Long,
8181
channel: SendChannel<Unit>
8282
) {
83-
var deadline = timeSource.nanoTime() + delayToNanos(initialDelayMillis)
83+
var deadline = nanoTime() + delayToNanos(initialDelayMillis)
8484
delay(initialDelayMillis)
8585
val delayNs = delayToNanos(delayMillis)
8686
while (true) {
8787
deadline += delayNs
8888
channel.send(Unit)
89-
val now = timeSource.nanoTime()
89+
val now = nanoTime()
9090
val nextDelay = (deadline - now).coerceAtLeast(0)
9191
if (nextDelay == 0L && delayNs != 0L) {
9292
val adjustedDelay = delayNs - (now - deadline) % delayNs

kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ internal class CoroutineScheduler(
339339
* @param fair whether the task should be dispatched fairly (strict FIFO) or not (semi-FIFO)
340340
*/
341341
fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, fair: Boolean = false) {
342-
timeSource.trackTask() // this is needed for virtual time support
342+
trackTask() // this is needed for virtual time support
343343
val task = createTask(block, taskContext)
344344
// try to submit the task to the local queue and act depending on the result
345345
when (submitToLocalQueue(task, fair)) {
@@ -596,7 +596,7 @@ internal class CoroutineScheduler(
596596
val thread = Thread.currentThread()
597597
thread.uncaughtExceptionHandler.uncaughtException(thread, e)
598598
} finally {
599-
timeSource.unTrackTask()
599+
unTrackTask()
600600
}
601601
}
602602

kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package kotlinx.coroutines
@@ -20,7 +20,7 @@ internal inline fun withVirtualTimeSource(log: PrintStream? = null, block: () ->
2020
} finally {
2121
DefaultExecutor.shutdown(SHUTDOWN_TIMEOUT)
2222
testTimeSource.shutdown()
23-
timeSource = DefaultTimeSource // restore time source
23+
timeSource = null // restore time source
2424
}
2525
}
2626

kotlinx-coroutines-core/jvm/test/guide/example-cancel-02.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
@@ -9,13 +9,13 @@ import kotlinx.coroutines.*
99

1010
fun main() = runBlocking {
1111
//sampleStart
12-
val startTime = timeSource.currentTimeMillis()
12+
val startTime = currentTimeMillis()
1313
val job = launch(Dispatchers.Default) {
1414
var nextPrintTime = startTime
1515
var i = 0
1616
while (i < 5) { // computation loop, just wastes CPU
1717
// print a message twice a second
18-
if (timeSource.currentTimeMillis() >= nextPrintTime) {
18+
if (currentTimeMillis() >= nextPrintTime) {
1919
println("job: I'm sleeping ${i++} ...")
2020
nextPrintTime += 500L
2121
}

kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
@@ -9,13 +9,13 @@ import kotlinx.coroutines.*
99

1010
fun main() = runBlocking {
1111
//sampleStart
12-
val startTime = timeSource.currentTimeMillis()
12+
val startTime = currentTimeMillis()
1313
val job = launch(Dispatchers.Default) {
1414
var nextPrintTime = startTime
1515
var i = 0
1616
while (isActive) { // cancellable computation loop
1717
// print a message twice a second
18-
if (timeSource.currentTimeMillis() >= nextPrintTime) {
18+
if (currentTimeMillis() >= nextPrintTime) {
1919
println("job: I'm sleeping ${i++} ...")
2020
nextPrintTime += 500L
2121
}

kotlinx-coroutines-core/jvm/test/guide/test/TestUtil.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package kotlinx.coroutines.guide.test
@@ -11,7 +11,7 @@ import org.junit.Assert.*
1111
import java.io.*
1212
import java.util.concurrent.*
1313

14-
fun wrapTask(block: Runnable) = timeSource.wrapTask(block)
14+
fun wrapTask(block: Runnable) = kotlinx.coroutines.wrapTask(block)
1515

1616
// helper function to dump exception to stdout for ease of debugging failed tests
1717
private inline fun <T> outputException(name: String, block: () -> T): T =

0 commit comments

Comments
 (0)