Roman Elizarov | 92b0485 | 2017-07-11 15:11:58 +0300 | [diff] [blame] | 1 | /* |
Roman Elizarov | 1f74a2d | 2018-06-29 19:19:45 +0300 | [diff] [blame^] | 2 | * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. |
Roman Elizarov | 92b0485 | 2017-07-11 15:11:58 +0300 | [diff] [blame] | 3 | */ |
| 4 | |
| 5 | package kotlinx.coroutines.experimental |
| 6 | |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 7 | import kotlinx.coroutines.experimental.timeunit.* |
| 8 | |
| 9 | internal actual val DefaultDelay: Delay = DefaultExecutor |
Roman Elizarov | 92b0485 | 2017-07-11 15:11:58 +0300 | [diff] [blame] | 10 | |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 11 | @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") |
| 12 | internal object DefaultExecutor : EventLoopBase(), Runnable { |
Roman Elizarov | 92b0485 | 2017-07-11 15:11:58 +0300 | [diff] [blame] | 13 | |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 14 | override val isCompleted: Boolean get() = false |
Roman Elizarov | 92b0485 | 2017-07-11 15:11:58 +0300 | [diff] [blame] | 15 | |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 16 | private const val DEFAULT_KEEP_ALIVE = 1000L // in milliseconds |
Roman Elizarov | 92b0485 | 2017-07-11 15:11:58 +0300 | [diff] [blame] | 17 | |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 18 | private val KEEP_ALIVE_NANOS = TimeUnit.MILLISECONDS.toNanos( |
Roman Elizarov | 92b0485 | 2017-07-11 15:11:58 +0300 | [diff] [blame] | 19 | try { |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 20 | java.lang.Long.getLong("kotlinx.coroutines.DefaultExecutor.keepAlive", DEFAULT_KEEP_ALIVE) |
| 21 | } catch (e: SecurityException) { |
| 22 | DEFAULT_KEEP_ALIVE |
| 23 | }) |
| 24 | |
| 25 | @Volatile |
| 26 | private var _thread: Thread? = null |
| 27 | |
| 28 | private const val FRESH = 0 |
| 29 | private const val ACTIVE = 1 |
| 30 | private const val SHUTDOWN_REQ = 2 |
| 31 | private const val SHUTDOWN_ACK = 3 |
| 32 | |
| 33 | @Volatile |
| 34 | private var debugStatus: Int = FRESH |
| 35 | |
Roman Elizarov | 9483301 | 2018-01-12 18:28:24 +0300 | [diff] [blame] | 36 | private val isShutdownRequested: Boolean get() { |
| 37 | val debugStatus = debugStatus |
| 38 | return debugStatus == SHUTDOWN_REQ || debugStatus == SHUTDOWN_ACK |
| 39 | } |
| 40 | |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 41 | override fun run() { |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 42 | timeSource.registerTimeLoopThread() |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 43 | try { |
Roman Elizarov | e8986b8 | 2018-01-11 18:06:30 +0300 | [diff] [blame] | 44 | var shutdownNanos = Long.MAX_VALUE |
Roman Elizarov | 9483301 | 2018-01-12 18:28:24 +0300 | [diff] [blame] | 45 | if (!notifyStartup()) return |
| 46 | while (true) { |
| 47 | Thread.interrupted() // just reset interruption flag |
| 48 | var parkNanos = processNextEvent() |
| 49 | if (parkNanos == Long.MAX_VALUE) { |
| 50 | // nothing to do, initialize shutdown timeout |
| 51 | if (shutdownNanos == Long.MAX_VALUE) { |
| 52 | val now = timeSource.nanoTime() |
| 53 | if (shutdownNanos == Long.MAX_VALUE) shutdownNanos = now + KEEP_ALIVE_NANOS |
| 54 | val tillShutdown = shutdownNanos - now |
| 55 | if (tillShutdown <= 0) return // shut thread down |
| 56 | parkNanos = parkNanos.coerceAtMost(tillShutdown) |
| 57 | } else |
| 58 | parkNanos = parkNanos.coerceAtMost(KEEP_ALIVE_NANOS) // limit wait time anyway |
| 59 | } |
| 60 | if (parkNanos > 0) { |
| 61 | // check if shutdown was requested and bail out in this case |
| 62 | if (isShutdownRequested) return |
| 63 | timeSource.parkNanos(this, parkNanos) |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 64 | } |
| 65 | } |
| 66 | } finally { |
| 67 | _thread = null // this thread is dead |
Roman Elizarov | 9483301 | 2018-01-12 18:28:24 +0300 | [diff] [blame] | 68 | acknowledgeShutdownIfNeeded() |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 69 | timeSource.unregisterTimeLoopThread() |
| 70 | // recheck if queues are empty after _thread reference was set to null (!!!) |
| 71 | if (!isEmpty) thread() // recreate thread if it is needed |
| 72 | } |
Roman Elizarov | 92b0485 | 2017-07-11 15:11:58 +0300 | [diff] [blame] | 73 | } |
| 74 | |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 75 | // ensure that thread is there |
| 76 | private fun thread(): Thread = _thread ?: createThreadSync() |
| 77 | |
| 78 | @Synchronized |
| 79 | private fun createThreadSync() = _thread ?: |
| 80 | Thread(this, "kotlinx.coroutines.DefaultExecutor").apply { |
| 81 | _thread = this |
| 82 | isDaemon = true |
| 83 | start() |
| 84 | } |
| 85 | |
| 86 | override fun unpark() { |
| 87 | timeSource.unpark(thread()) // as a side effect creates thread if it is not there |
| 88 | } |
| 89 | |
| 90 | override fun isCorrectThread(): Boolean = true |
| 91 | |
| 92 | // used for tests |
| 93 | @Synchronized |
| 94 | internal fun ensureStarted() { |
| 95 | assert(_thread == null) // ensure we are at a clean state |
Roman Elizarov | e8986b8 | 2018-01-11 18:06:30 +0300 | [diff] [blame] | 96 | assert(debugStatus == FRESH || debugStatus == SHUTDOWN_ACK) |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 97 | debugStatus = FRESH |
| 98 | createThreadSync() // create fresh thread |
| 99 | while (debugStatus == FRESH) (this as Object).wait() |
| 100 | } |
| 101 | |
| 102 | @Synchronized |
Roman Elizarov | e8986b8 | 2018-01-11 18:06:30 +0300 | [diff] [blame] | 103 | private fun notifyStartup(): Boolean { |
Roman Elizarov | 9483301 | 2018-01-12 18:28:24 +0300 | [diff] [blame] | 104 | if (isShutdownRequested) return false |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 105 | debugStatus = ACTIVE |
| 106 | (this as Object).notifyAll() |
Roman Elizarov | e8986b8 | 2018-01-11 18:06:30 +0300 | [diff] [blame] | 107 | return true |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 108 | } |
| 109 | |
| 110 | // used for tests |
| 111 | @Synchronized |
Roman Elizarov | 9483301 | 2018-01-12 18:28:24 +0300 | [diff] [blame] | 112 | fun shutdown(timeout: Long) { |
| 113 | val deadline = System.currentTimeMillis() + timeout |
| 114 | if (!isShutdownRequested) debugStatus = SHUTDOWN_REQ |
| 115 | // loop while there is anything to do immediately or deadline passes |
| 116 | while (debugStatus != SHUTDOWN_ACK && _thread != null) { |
| 117 | _thread?.let { timeSource.unpark(it) } // wake up thread if present |
| 118 | val remaining = deadline - System.currentTimeMillis() |
| 119 | if (remaining <= 0) break |
| 120 | (this as Object).wait(timeout) |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 121 | } |
| 122 | // restore fresh status |
| 123 | debugStatus = FRESH |
| 124 | } |
| 125 | |
| 126 | @Synchronized |
Roman Elizarov | 9483301 | 2018-01-12 18:28:24 +0300 | [diff] [blame] | 127 | private fun acknowledgeShutdownIfNeeded() { |
| 128 | if (!isShutdownRequested) return |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 129 | debugStatus = SHUTDOWN_ACK |
Roman Elizarov | 5d94a26 | 2017-12-28 00:23:39 +0300 | [diff] [blame] | 130 | resetAll() // clear queues |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 131 | (this as Object).notifyAll() |
Roman Elizarov | 92b0485 | 2017-07-11 15:11:58 +0300 | [diff] [blame] | 132 | } |
| 133 | } |