@@ -72,9 +72,12 @@ public fun EventLoop(thread: Thread = Thread.currentThread(), parentJob: Job? =
72
72
public fun EventLoop_Deprecated (thread : Thread = Thread .currentThread(), parentJob : Job ? = null): CoroutineDispatcher =
73
73
EventLoop (thread, parentJob) as CoroutineDispatcher
74
74
75
- internal const val DELAYED = 0
76
- internal const val REMOVED = 1
77
- internal const val RESCHEDULED = 2
75
+ private val DISPOSED_TASK = Symbol (" REMOVED_TASK" )
76
+
77
+ // results for scheduleImpl
78
+ private const val SCHEDULE_OK = 0
79
+ private const val SCHEDULE_COMPLETED = 1
80
+ private const val SCHEDULE_DISPOSED = 2
78
81
79
82
private const val MS_TO_NS = 1_000_000L
80
83
private const val MAX_MS = Long .MAX_VALUE / MS_TO_NS
@@ -242,22 +245,23 @@ internal abstract class EventLoopBase: CoroutineDispatcher(), Delay, EventLoop {
242
245
}
243
246
244
247
internal fun schedule (delayedTask : DelayedTask ) {
245
- if (scheduleImpl(delayedTask)) {
246
- if (shouldUnpark(delayedTask)) unpark()
247
- } else {
248
- DefaultExecutor .schedule(delayedTask)
248
+ when (scheduleImpl(delayedTask)) {
249
+ SCHEDULE_OK -> if (shouldUnpark(delayedTask)) unpark()
250
+ SCHEDULE_COMPLETED -> DefaultExecutor .schedule(delayedTask)
251
+ SCHEDULE_DISPOSED -> {} // do nothing -- task was already disposed
252
+ else -> error(" unexpected result" )
249
253
}
250
254
}
251
255
252
256
private fun shouldUnpark (task : DelayedTask ): Boolean = _delayed .value?.peek() == = task
253
257
254
- private fun scheduleImpl (delayedTask : DelayedTask ): Boolean {
255
- if (isCompleted) return false
258
+ private fun scheduleImpl (delayedTask : DelayedTask ): Int {
259
+ if (isCompleted) return SCHEDULE_COMPLETED
256
260
val delayed = _delayed .value ? : run {
257
261
_delayed .compareAndSet(null , ThreadSafeHeap ())
258
262
_delayed .value!!
259
263
}
260
- return delayed.addLastIf(delayedTask) { ! isCompleted }
264
+ return delayedTask.schedule(delayed)
261
265
}
262
266
263
267
internal fun removeDelayedImpl (delayedTask : DelayedTask ) {
@@ -273,6 +277,13 @@ internal abstract class EventLoopBase: CoroutineDispatcher(), Delay, EventLoop {
273
277
// This is a "soft" (normal) shutdown
274
278
protected fun rescheduleAllDelayed () {
275
279
while (true ) {
280
+ /*
281
+ * `removeFirstOrNull` below is the only operation on DelayedTask & ThreadSafeHeap that is not
282
+ * synchronized on DelayedTask itself. All other operation are synchronized both on
283
+ * DelayedTask & ThreadSafeHeap instances (in this order). It is still safe, because `dispose`
284
+ * first removes DelayedTask from the heap (under synchronization) then
285
+ * assign "_heap = DISPOSED_TASK", so there cannot be ever a race to _heap reference update.
286
+ */
276
287
val delayedTask = _delayed .value?.removeFirstOrNull() ? : break
277
288
delayedTask.rescheduleOnShutdown()
278
289
}
@@ -281,8 +292,17 @@ internal abstract class EventLoopBase: CoroutineDispatcher(), Delay, EventLoop {
281
292
internal abstract inner class DelayedTask (
282
293
timeMillis : Long
283
294
) : Runnable, Comparable<DelayedTask>, DisposableHandle, ThreadSafeHeapNode {
295
+ private var _heap : Any? = null // null | ThreadSafeHeap | DISPOSED_TASK
296
+
297
+ override var heap: ThreadSafeHeap <* >?
298
+ get() = _heap as ? ThreadSafeHeap <* >
299
+ set(value) {
300
+ require(_heap != = DISPOSED_TASK ) // this can never happen, it is always checked before adding/removing
301
+ _heap = value
302
+ }
303
+
284
304
override var index: Int = - 1
285
- @JvmField var state = DELAYED // Guarded by by lock on this task for reschedule/dispose purposes
305
+
286
306
@JvmField val nanoTime: Long = timeSource.nanoTime() + delayToNanos(timeMillis)
287
307
288
308
override fun compareTo (other : DelayedTask ): Int {
@@ -297,24 +317,21 @@ internal abstract class EventLoopBase: CoroutineDispatcher(), Delay, EventLoop {
297
317
fun timeToExecute (now : Long ): Boolean = now - nanoTime >= 0L
298
318
299
319
@Synchronized
300
- fun rescheduleOnShutdown () {
301
- if (state != DELAYED ) return
302
- if (_delayed .value!! .remove(this )) {
303
- state = RESCHEDULED
304
- DefaultExecutor .schedule(this )
305
- } else {
306
- state = REMOVED
307
- }
320
+ fun schedule (delayed : ThreadSafeHeap <DelayedTask >): Int {
321
+ if (_heap == = DISPOSED_TASK ) return SCHEDULE_DISPOSED // don't add -- was already disposed
322
+ return if (delayed.addLastIf(this ) { ! isCompleted }) SCHEDULE_OK else SCHEDULE_COMPLETED
308
323
}
309
324
325
+ // note: DefaultExecutor.schedule performs `schedule` (above) which does sync & checks for DISPOSED_TASK
326
+ fun rescheduleOnShutdown () = DefaultExecutor .schedule(this )
327
+
310
328
@Synchronized
311
329
final override fun dispose () {
312
- when (state) {
313
- DELAYED -> _delayed .value?.remove(this )
314
- RESCHEDULED -> DefaultExecutor .removeDelayedImpl(this )
315
- else -> return
316
- }
317
- state = REMOVED
330
+ val heap = _heap
331
+ if (heap == = DISPOSED_TASK ) return // already disposed
332
+ @Suppress(" UNCHECKED_CAST" )
333
+ (heap as ? ThreadSafeHeap <DelayedTask >)?.remove(this ) // remove if it is in heap (first)
334
+ _heap = DISPOSED_TASK // never add again to any heap
318
335
}
319
336
320
337
override fun toString (): String = " Delayed[nanos=$nanoTime ]"
0 commit comments