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