blob: 041aa9cf6c406aaebd59b82ccd417695e1f1ef70 [file] [log] [blame]
Roman Elizarovf16fd272017-02-07 11:26:00 +03001/*
2 * Copyright 2016-2017 JetBrains s.r.o.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Roman Elizarov53a0a402017-01-19 20:21:57 +030017package kotlinx.coroutines.experimental
18
19import kotlinx.coroutines.experimental.internal.LockFreeLinkedListHead
20import kotlinx.coroutines.experimental.internal.LockFreeLinkedListNode
21import java.util.concurrent.locks.LockSupport
Roman Elizarovea4a51b2017-01-31 12:01:25 +030022import kotlin.coroutines.experimental.CoroutineContext
Roman Elizarov53a0a402017-01-19 20:21:57 +030023
Roman Elizarovd528e3e2017-01-23 15:40:05 +030024/**
25 * 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
27 * continue processing events when invoked from the event dispatch thread.
28 */
Roman Elizarov53a0a402017-01-19 20:21:57 +030029public interface EventLoop {
Roman Elizarovd528e3e2017-01-23 15:40:05 +030030 /**
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.
33 */
34 public fun processNextEvent(): Boolean
35
36 public companion object Factory {
37 /**
38 * Creates a new event loop that is bound the specified [thread] (current thread by default) and
39 * stops accepting new events when [parentJob] completes. Every continuation that is scheduled
40 * onto this event loop unparks the specified thread via [LockSupport.unpark].
41 *
42 * The main event-processing loop using the resulting `eventLoop` object should look like this:
43 * ```
44 * while (needsToBeRunning) {
45 * if (Thread.interrupted()) break // or handle somehow
46 * if (!eventLoop.processNextEvent()) LockSupport.park() // event loop will unpark
47 * }
48 * ```
49 */
50 public operator fun invoke(thread: Thread = Thread.currentThread(), parentJob: Job? = null): CoroutineDispatcher =
51 EventLoopImpl(thread).apply {
52 if (parentJob != null) initParentJob(parentJob)
53 }
54 }
Roman Elizarov53a0a402017-01-19 20:21:57 +030055}
56
Roman Elizarovd528e3e2017-01-23 15:40:05 +030057internal class EventLoopImpl(
58 val thread: Thread
Roman Elizarov7cf452e2017-01-29 21:58:33 +030059) : CoroutineDispatcher(), EventLoop {
Roman Elizarov53a0a402017-01-19 20:21:57 +030060 val queue = LockFreeLinkedListHead()
Roman Elizarovd528e3e2017-01-23 15:40:05 +030061 var parentJob: Job? = null
Roman Elizarov53a0a402017-01-19 20:21:57 +030062
Roman Elizarovd528e3e2017-01-23 15:40:05 +030063 fun initParentJob(coroutine: Job) {
64 require(this.parentJob == null)
65 this.parentJob = coroutine
Roman Elizarov53a0a402017-01-19 20:21:57 +030066 }
67
Roman Elizarov67891d82017-01-23 16:47:20 +030068 override fun dispatch(context: CoroutineContext, block: Runnable) {
Roman Elizarov53a0a402017-01-19 20:21:57 +030069 schedule(Dispatch(block))
Roman Elizarov53a0a402017-01-19 20:21:57 +030070 }
71
Roman Elizarovd528e3e2017-01-23 15:40:05 +030072 fun schedule(node: Node): Boolean {
73 val added = if (parentJob == null) {
74 queue.addLast(node)
75 true
76 } else
Roman Elizarov32d95322017-02-09 15:57:31 +030077 queue.addLastIf(node) { !parentJob!!.isCompleted }
Roman Elizarovd528e3e2017-01-23 15:40:05 +030078 if (added) {
Roman Elizarov7cf452e2017-01-29 21:58:33 +030079 if (Thread.currentThread() !== thread)
Roman Elizarova4becc92017-01-24 11:45:50 +030080 LockSupport.unpark(thread)
Roman Elizarovd528e3e2017-01-23 15:40:05 +030081 } else {
82 node.run()
Roman Elizarov53a0a402017-01-19 20:21:57 +030083 }
Roman Elizarovd528e3e2017-01-23 15:40:05 +030084 return added
Roman Elizarov53a0a402017-01-19 20:21:57 +030085 }
86
Roman Elizarovd528e3e2017-01-23 15:40:05 +030087 override fun processNextEvent(): Boolean {
Roman Elizarov7cf452e2017-01-29 21:58:33 +030088 if (Thread.currentThread() !== thread) return false
Roman Elizarovd528e3e2017-01-23 15:40:05 +030089 (queue.removeFirstOrNull() as? Runnable)?.apply {
90 run()
91 return true
92 }
93 return false
94 }
Roman Elizarov53a0a402017-01-19 20:21:57 +030095
Roman Elizarovd528e3e2017-01-23 15:40:05 +030096 abstract class Node : LockFreeLinkedListNode(), Runnable
97
98 class Dispatch(block: Runnable) : Node(), Runnable by block
Roman Elizarov32d95322017-02-09 15:57:31 +030099
100 override fun toString(): String = "EventLoopImpl@${Integer.toHexString(System.identityHashCode(this))}"
Roman Elizarov53a0a402017-01-19 20:21:57 +0300101}
102