Roman Elizarov | f16fd27 | 2017-02-07 11:26:00 +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 | f16fd27 | 2017-02-07 11:26:00 +0300 | [diff] [blame] | 3 | */ |
| 4 | |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 5 | @file:JvmMultifileClass |
| 6 | @file:JvmName("BuildersKt") |
Roman Elizarov | f161c9f | 2018-02-07 23:27:10 +0300 | [diff] [blame] | 7 | |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 8 | package kotlinx.coroutines.experimental |
| 9 | |
Roman Elizarov | 2adf8bc | 2018-01-24 20:09:57 +0300 | [diff] [blame] | 10 | import java.util.concurrent.locks.* |
Roman Elizarov | ea4a51b | 2017-01-31 12:01:25 +0300 | [diff] [blame] | 11 | import kotlin.coroutines.experimental.* |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 12 | |
| 13 | /** |
Roman Elizarov | 32d9532 | 2017-02-09 15:57:31 +0300 | [diff] [blame] | 14 | * Runs new coroutine and **blocks** current thread _interruptibly_ until its completion. |
Roman Elizarov | d528e3e | 2017-01-23 15:40:05 +0300 | [diff] [blame] | 15 | * This function should not be used from coroutine. It is designed to bridge regular blocking code |
| 16 | * to libraries that are written in suspending style, to be used in `main` functions and in tests. |
| 17 | * |
| 18 | * The default [CoroutineDispatcher] for this builder in an implementation of [EventLoop] that processes continuations |
| 19 | * in this blocked thread until the completion of this coroutine. |
| 20 | * See [CoroutineDispatcher] for the other implementations that are provided by `kotlinx.coroutines`. |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 21 | * |
Roman Elizarov | cb78787 | 2018-01-29 18:07:21 +0300 | [diff] [blame] | 22 | * When [CoroutineDispatcher] is explicitly specified in the [context], then the new coroutine runs in the context of |
| 23 | * the specified dispatcher while the current thread is blocked. If the specified dispatcher implements [EventLoop] |
| 24 | * interface and this `runBlocking` invocation is performed from inside of the this event loop's thread, then |
| 25 | * this event loop is processed using its [processNextEvent][EventLoop.processNextEvent] method until coroutine completes. |
| 26 | * |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 27 | * If this blocked thread is interrupted (see [Thread.interrupt]), then the coroutine job is cancelled and |
| 28 | * this `runBlocking` invocation throws [InterruptedException]. |
| 29 | * |
| 30 | * See [newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine. |
Roman Elizarov | c0d559b | 2017-09-28 14:27:05 +0300 | [diff] [blame] | 31 | * |
| 32 | * @param context context of the coroutine. The default value is an implementation of [EventLoop]. |
| 33 | * @param block the coroutine code. |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 34 | */ |
| 35 | @Throws(InterruptedException::class) |
Roman Elizarov | bddb1d7 | 2017-12-25 17:16:08 +0300 | [diff] [blame] | 36 | public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T { |
Roman Elizarov | d528e3e | 2017-01-23 15:40:05 +0300 | [diff] [blame] | 37 | val currentThread = Thread.currentThread() |
Roman Elizarov | cb78787 | 2018-01-29 18:07:21 +0300 | [diff] [blame] | 38 | val contextInterceptor = context[ContinuationInterceptor] |
| 39 | val privateEventLoop = contextInterceptor == null // create private event loop if no dispatcher is specified |
| 40 | val eventLoop = if (privateEventLoop) BlockingEventLoop(currentThread) else contextInterceptor as? EventLoop |
| 41 | val newContext = newCoroutineContext( |
| 42 | if (privateEventLoop) context + (eventLoop as ContinuationInterceptor) else context |
| 43 | ) |
| 44 | val coroutine = BlockingCoroutine<T>(newContext, currentThread, eventLoop, privateEventLoop) |
Roman Elizarov | 2adf8bc | 2018-01-24 20:09:57 +0300 | [diff] [blame] | 45 | coroutine.start(CoroutineStart.DEFAULT, coroutine, block) |
Roman Elizarov | d528e3e | 2017-01-23 15:40:05 +0300 | [diff] [blame] | 46 | return coroutine.joinBlocking() |
| 47 | } |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 48 | |
Roman Elizarov | d528e3e | 2017-01-23 15:40:05 +0300 | [diff] [blame] | 49 | private class BlockingCoroutine<T>( |
Roman Elizarov | 2b12d58 | 2017-06-22 20:12:19 +0300 | [diff] [blame] | 50 | parentContext: CoroutineContext, |
Roman Elizarov | 4518e05 | 2017-03-02 22:48:27 +0300 | [diff] [blame] | 51 | private val blockedThread: Thread, |
Roman Elizarov | cb78787 | 2018-01-29 18:07:21 +0300 | [diff] [blame] | 52 | private val eventLoop: EventLoop?, |
Roman Elizarov | 4518e05 | 2017-03-02 22:48:27 +0300 | [diff] [blame] | 53 | private val privateEventLoop: Boolean |
Roman Elizarov | 2b12d58 | 2017-06-22 20:12:19 +0300 | [diff] [blame] | 54 | ) : AbstractCoroutine<T>(parentContext, true) { |
Roman Elizarov | 4518e05 | 2017-03-02 22:48:27 +0300 | [diff] [blame] | 55 | init { |
Roman Elizarov | 8b38fa2 | 2017-09-27 17:44:31 +0300 | [diff] [blame] | 56 | if (privateEventLoop) require(eventLoop is BlockingEventLoop) |
Roman Elizarov | 4518e05 | 2017-03-02 22:48:27 +0300 | [diff] [blame] | 57 | } |
| 58 | |
Roman Elizarov | ebc8866 | 2018-01-24 23:58:56 +0300 | [diff] [blame] | 59 | override fun onCancellationInternal(exceptionally: CompletedExceptionally?) { |
Roman Elizarov | 8b38fa2 | 2017-09-27 17:44:31 +0300 | [diff] [blame] | 60 | // wake up blocked thread |
Roman Elizarov | a4becc9 | 2017-01-24 11:45:50 +0300 | [diff] [blame] | 61 | if (Thread.currentThread() != blockedThread) |
| 62 | LockSupport.unpark(blockedThread) |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 63 | } |
| 64 | |
| 65 | @Suppress("UNCHECKED_CAST") |
| 66 | fun joinBlocking(): T { |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 67 | timeSource.registerTimeLoopThread() |
Roman Elizarov | 4518e05 | 2017-03-02 22:48:27 +0300 | [diff] [blame] | 68 | while (true) { |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 69 | if (Thread.interrupted()) throw InterruptedException().also { cancel(it) } |
Roman Elizarov | 4518e05 | 2017-03-02 22:48:27 +0300 | [diff] [blame] | 70 | val parkNanos = eventLoop?.processNextEvent() ?: Long.MAX_VALUE |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 71 | // note: process next even may loose unpark flag, so check if completed before parking |
Roman Elizarov | 7400ff0 | 2017-07-10 17:56:41 +0300 | [diff] [blame] | 72 | if (isCompleted) break |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 73 | timeSource.parkNanos(this, parkNanos) |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 74 | } |
Roman Elizarov | 4518e05 | 2017-03-02 22:48:27 +0300 | [diff] [blame] | 75 | // process queued events (that could have been added after last processNextEvent and before cancel |
Roman Elizarov | 0d35c85 | 2017-10-07 23:19:22 +0300 | [diff] [blame] | 76 | if (privateEventLoop) (eventLoop as BlockingEventLoop).apply { |
| 77 | // We exit the "while" loop above when this coroutine's state "isCompleted", |
| 78 | // Here we should signal that BlockingEventLoop should not accept any more tasks |
| 79 | isCompleted = true |
| 80 | shutdown() |
| 81 | } |
Roman Elizarov | 35d2c34 | 2017-07-20 14:54:39 +0300 | [diff] [blame] | 82 | timeSource.unregisterTimeLoopThread() |
Roman Elizarov | d528e3e | 2017-01-23 15:40:05 +0300 | [diff] [blame] | 83 | // now return result |
Roman Elizarov | ee7c0eb | 2017-02-16 15:29:28 +0300 | [diff] [blame] | 84 | val state = this.state |
Vsevolod Tolstopyatov | c1092d5 | 2018-04-12 20:22:25 +0300 | [diff] [blame] | 85 | (state as? CompletedExceptionally)?.let { throw it.cause } |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 86 | return state as T |
| 87 | } |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 88 | } |