@@ -18,20 +18,29 @@ package kotlinx.coroutines.experimental
18
18
19
19
import kotlinx.coroutines.experimental.internal.LockFreeLinkedListHead
20
20
import kotlinx.coroutines.experimental.internal.LockFreeLinkedListNode
21
+ import java.util.concurrent.ConcurrentSkipListMap
22
+ import java.util.concurrent.TimeUnit
23
+ import java.util.concurrent.atomic.AtomicLong
21
24
import java.util.concurrent.locks.LockSupport
22
25
import kotlin.coroutines.experimental.CoroutineContext
23
26
24
27
/* *
25
28
* Implemented by [CoroutineDispatcher] implementations that have event loop inside and can
26
- * be asked to process next event from their event queue. It is used by [runBlocking] to
29
+ * be asked to process next event from their event queue.
30
+ *
31
+ * It may optionally implement [Delay] interface and support time-scheduled tasks. It is used by [runBlocking] to
27
32
* continue processing events when invoked from the event dispatch thread.
28
33
*/
29
34
public interface EventLoop {
30
35
/* *
31
- * Processes next event in this event loop and returns `true` or returns `false` if there are
32
- * no events to process or when invoked from the wrong thread.
36
+ * Processes next event in this event loop.
37
+ *
38
+ * The result of this function is to be interpreted like this:
39
+ * * `<= 0` -- there are potentially more events for immediate processing;
40
+ * * `> 0` -- a number of nanoseconds to wait for next scheduled event;
41
+ * * [Long.MAX_VALUE] -- no more events, or was invoked from the wrong thread.
33
42
*/
34
- public fun processNextEvent (): Boolean
43
+ public fun processNextEvent (): Long
35
44
36
45
public companion object Factory {
37
46
/* *
@@ -43,7 +52,7 @@ public interface EventLoop {
43
52
* ```
44
53
* while (needsToBeRunning) {
45
54
* if (Thread.interrupted()) break // or handle somehow
46
- * if (! eventLoop.processNextEvent()) LockSupport.park( ) // event loop will unpark
55
+ * LockSupport.parkNanos( eventLoop.processNextEvent()) // event loop will unpark
47
56
* }
48
57
* ```
49
58
*/
@@ -55,48 +64,140 @@ public interface EventLoop {
55
64
}
56
65
57
66
internal class EventLoopImpl (
58
- val thread : Thread
59
- ) : CoroutineDispatcher(), EventLoop {
60
- val queue = LockFreeLinkedListHead ()
61
- var parentJob: Job ? = null
67
+ private val thread : Thread
68
+ ) : CoroutineDispatcher(), EventLoop, Delay {
69
+ private val queue = LockFreeLinkedListHead ()
70
+ private val delayed = ConcurrentSkipListMap <DelayedTask , DelayedTask >()
71
+ private val nextSequence = AtomicLong ()
72
+ private var parentJob: Job ? = null
62
73
63
74
fun initParentJob (coroutine : Job ) {
64
75
require(this .parentJob == null )
65
76
this .parentJob = coroutine
66
77
}
67
78
68
79
override fun dispatch (context : CoroutineContext , block : Runnable ) {
69
- schedule(Dispatch (block))
80
+ if (scheduleQueued(QueuedRunnableTask (block))) {
81
+ unpark()
82
+ } else {
83
+ block.run ()
84
+ }
70
85
}
71
86
72
- fun schedule (node : Node ): Boolean {
73
- val added = if (parentJob == null ) {
74
- queue.addLast(node)
75
- true
76
- } else
77
- queue.addLastIf(node) { ! parentJob!! .isCompleted }
78
- if (added) {
79
- if (Thread .currentThread() != = thread)
80
- LockSupport .unpark(thread)
87
+ override fun scheduleResumeAfterDelay (time : Long , unit : TimeUnit , continuation : CancellableContinuation <Unit >) {
88
+ if (scheduleDelayed(DelayedResumeTask (time, unit, continuation))) {
89
+ // todo: we should unpark only when this delayed task became first in the queue
90
+ unpark()
81
91
} else {
82
- node. run ( )
92
+ scheduledExecutor.schedule( ResumeRunnable (continuation), time, unit )
83
93
}
84
- return added
85
94
}
86
95
87
- override fun processNextEvent (): Boolean {
88
- if (Thread .currentThread() != = thread) return false
89
- (queue.removeFirstOrNull() as ? Runnable )?.apply {
90
- run ()
96
+ override fun invokeOnTimeout (time : Long , unit : TimeUnit , block : Runnable ): DisposableHandle =
97
+ DelayedRunnableTask (time, unit, block).also { scheduleDelayed(it) }
98
+
99
+ override fun processNextEvent (): Long {
100
+ if (Thread .currentThread() != = thread) return Long .MAX_VALUE
101
+ // queue all delayed tasks that are due to be executed
102
+ while (true ) {
103
+ val delayedTask = delayed.firstEntry()?.key ? : break
104
+ val now = System .nanoTime()
105
+ if (delayedTask.nanoTime - now > 0 ) break
106
+ if (! scheduleQueued(delayedTask)) break
107
+ delayed.remove(delayedTask)
108
+ }
109
+ // then process one event from queue
110
+ (queue.removeFirstOrNull() as ? QueuedTask )?.let { queuedTask ->
111
+ queuedTask()
112
+ }
113
+ if (! queue.isEmpty) return 0
114
+ val nextDelayedTask = delayed.firstEntry()?.key ? : return Long .MAX_VALUE
115
+ return nextDelayedTask.nanoTime - System .nanoTime()
116
+ }
117
+
118
+ fun shutdown () {
119
+ // complete processing of all queued tasks
120
+ while (true ) {
121
+ val queuedTask = (queue.removeFirstOrNull() ? : break ) as QueuedTask
122
+ queuedTask()
123
+ }
124
+ // cancel all delayed tasks
125
+ while (true ) {
126
+ val delayedTask = delayed.pollFirstEntry()?.key ? : break
127
+ delayedTask.cancel()
128
+ }
129
+ }
130
+
131
+ override fun toString (): String = " EventLoopImpl@${Integer .toHexString(System .identityHashCode(this ))} "
132
+
133
+ private fun scheduleQueued (queuedTask : QueuedTask ): Boolean {
134
+ if (parentJob == null ) {
135
+ queue.addLast(queuedTask)
91
136
return true
92
137
}
138
+ return queue.addLastIf(queuedTask, { ! parentJob!! .isCompleted })
139
+ }
140
+
141
+ private fun scheduleDelayed (delayedTask : DelayedTask ): Boolean {
142
+ delayed.put(delayedTask, delayedTask)
143
+ if (parentJob?.isActive != false ) return true
144
+ delayedTask.dispose()
93
145
return false
94
146
}
95
147
96
- abstract class Node : LockFreeLinkedListNode (), Runnable
148
+ private fun unpark () {
149
+ if (Thread .currentThread() != = thread)
150
+ LockSupport .unpark(thread)
151
+ }
97
152
98
- class Dispatch ( block : Runnable ) : Node (), Runnable by block
153
+ private abstract class QueuedTask : LockFreeLinkedListNode (), () -> Unit
99
154
100
- override fun toString (): String = " EventLoopImpl@${Integer .toHexString(System .identityHashCode(this ))} "
101
- }
155
+ private class QueuedRunnableTask (
156
+ private val block : Runnable
157
+ ) : QueuedTask() {
158
+ override fun invoke () { block.run () }
159
+ }
160
+
161
+ private abstract inner class DelayedTask (
162
+ time : Long , timeUnit : TimeUnit
163
+ ) : QueuedTask(), Comparable<DelayedTask>, DisposableHandle {
164
+ @JvmField val nanoTime: Long = System .nanoTime() + timeUnit.toNanos(time)
165
+ @JvmField val sequence: Long = nextSequence.getAndIncrement()
166
+
167
+ override fun compareTo (other : DelayedTask ): Int {
168
+ val dTime = nanoTime - other.nanoTime
169
+ if (dTime > 0 ) return 1
170
+ if (dTime < 0 ) return - 1
171
+ val dSequence = sequence - other.sequence
172
+ return if (dSequence > 0 ) 1 else if (dSequence < 0 ) - 1 else 0
173
+ }
174
+
175
+ override final fun dispose () {
176
+ delayed.remove(this )
177
+ cancel()
178
+ }
102
179
180
+ open fun cancel () {}
181
+ }
182
+
183
+ private inner class DelayedResumeTask (
184
+ time : Long , timeUnit : TimeUnit ,
185
+ private val cont : CancellableContinuation <Unit >
186
+ ) : DelayedTask(time, timeUnit) {
187
+ override fun invoke () {
188
+ with (cont) { resumeUndispatched(Unit ) }
189
+ }
190
+ override fun cancel () {
191
+ if (! cont.isActive) return
192
+ val remaining = nanoTime - System .nanoTime()
193
+ scheduledExecutor.schedule(ResumeRunnable (cont), remaining, TimeUnit .NANOSECONDS )
194
+ }
195
+ }
196
+
197
+ private inner class DelayedRunnableTask (
198
+ time : Long , timeUnit : TimeUnit ,
199
+ private val block : Runnable
200
+ ) : DelayedTask(time, timeUnit) {
201
+ override fun invoke () { block.run () }
202
+ }
203
+ }
0 commit comments