├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── kotlin-coroutines.iml ├── libraries │ └── KotlinJavaRuntime.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── LICENSE ├── README.md ├── disposable-iterators.md ├── examples ├── channel │ ├── channel-example-1.kt │ ├── channel-example-2.kt │ ├── channel-example-2a.kt │ ├── channel-example-3.kt │ ├── channel-example-4.kt │ ├── channel-example-5.kt │ ├── channel-example-6.kt │ ├── channel-example-8.kt │ ├── channel-example-9.kt │ ├── channel-example-boring.kt │ ├── channel-example-multiplexing.kt │ ├── channel-example-multiplxing2.kt │ ├── channel.kt │ ├── go.kt │ ├── select.kt │ └── time.kt ├── context │ ├── auth-example.kt │ ├── auth.kt │ ├── pool-example.kt │ ├── pool.kt │ ├── swing-delay-example.kt │ ├── swing-delay.kt │ ├── swing-example.kt │ ├── swing.kt │ ├── threadContext-example.kt │ └── threadContext.kt ├── delay │ ├── delay-example.kt │ └── delay.kt ├── future │ ├── await.kt │ ├── future-example.kt │ └── future.kt ├── generator │ ├── generator-test1.kt │ ├── generator-test2.kt │ ├── generator-test3.kt │ └── generator.kt ├── io │ ├── io-example.kt │ └── io.kt ├── mutex │ └── mutex.kt ├── run │ ├── launch.kt │ └── runBlocking.kt ├── sequence │ ├── fibonacci.kt │ ├── optimized │ │ ├── sequenceOptimized-test.kt │ │ └── sequenceOptimized.kt │ ├── sequence.kt │ └── sequenceOfLines.kt ├── suspendingSequence │ ├── suspendingSequence-example.kt │ └── suspendingSequence.kt └── util │ └── log.kt └── kotlin-coroutines-informal.md /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | .idea/workspace.xml 3 | .idea/uiDesiner.xml -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | kotlin-coroutines -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/kotlin-coroutines.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/libraries/KotlinJavaRuntime.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kotlin Coroutines Examples 2 | 3 | This repository contains examples for Kotlin Coroutines Design. 4 | 5 | ## Navigate 6 | 7 | * [Kotlin Coroutines Design](https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md) (KEEP for Kotlin Coroutines) 8 | * [Coroutine Reference Guide](https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html) (User-level documentation) 9 | -------------------------------------------------------------------------------- /disposable-iterators.md: -------------------------------------------------------------------------------- 1 | # Disposable iterators 2 | 3 | * **Type**: Design proposal 4 | * **Author**: Andrey Breslav 5 | * **Contributors**: Vladimir Reshetnikov, Stanislav Erokhin 6 | * **Status**: Under consideration 7 | * **Prototype**: Not started 8 | 9 | ## Feedback 10 | 11 | Discussion of this proposal is held in [this issue](https://github.com/Kotlin/kotlin-coroutines/issues/10). 12 | 13 | ## Synopsis 14 | 15 | An iterator (for example, one generated by a coroutine) may iterate over a file or some other disposable resource. If the iteration completes normally (i.e. by reaching the last item), the iterator can dispose the underlying resource, but if 16 | 17 | - an exception occurs during one of the iterations, 18 | - `break`, `continue` or `return` cause early termination of the loop, 19 | 20 | the resource will never be disposed. 21 | 22 | To overcome this issue, C# has all `for`-loops wrapped in `try`/`finally`, where the `finally` block checks whether the iterator implements `IDisposable` and if so, calls the `Dispose()` method. 23 | 24 | Here we propose the same for Kotlin. 25 | 26 | ## References 27 | 28 | - Description: [Handling `finally` blocks in Kotlin coroutines](kotlin-coroutines-informal.md#handling-finally-blocks) 29 | - Issue #1: [Handling of `finally` blocks](https://github.com/Kotlin/kotlin-coroutines/issues/1) 30 | - Issue #9: [Allow suspension points in `finally` blocks?](https://github.com/Kotlin/kotlin-coroutines/issues/9) 31 | - C# [Iterator block implementation details: auto-generated state machines](https://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx) 32 | 33 | ## Implementation 34 | 35 | The idea is to wrap every `for`-loop that uses an iterator into a `try`/`finally`. The handler in the `finally` block is calling the following function (to be added to `kotlin-runtime`): 36 | 37 | ``` kotlin 38 | internal fun disposeIfNeeded(obj: Any?) { 39 | if (obj is Disposable) { 40 | obj.dispose() 41 | } 42 | } 43 | ``` 44 | 45 | The aforementioned `Disposable` interface should be added to `kotlin-runtime` as follows: 46 | 47 | ``` kotlin 48 | package kotlin 49 | 50 | public interface Disposable { 51 | fun dispose() 52 | } 53 | ``` 54 | 55 | The Standard Library code should be revised and any iteration utilities there that iterate without using `for`-loops must be updated to provide the same semantics. 56 | 57 | ## JVM costs 58 | 59 | Apart from adding these two items to `kotlin-runtime` this results in generating extra byte code instructions for every `for`-loop that uses an iterator (note that loops that enumerate number ranges don't use iterators most of the time). This will amount to a minimum of 6 instructions per `for`-loop (+ `TRYCATCHBLOCK` entries in the [Code Attribute](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.3)). To be precise, this amounts to two instructions per copy of the `finally` block): 60 | ``` 61 | ALOAD 1 # iterator 62 | INVOKESTATIC disposeIfNeeded(Ljava/lang/Object;)V 63 | ``` 64 | 65 | There are at least two copies of the `finally` block: one for normal termination, and another for exceptional termination. An extra copy is generated for each exit point, such as `break`, `continue` and `return` inside the loop. 66 | 67 | Another two instructions need to be added for the implicit `catch` block (which has to be generated to implement the `try`/`finally` semantics): one to jump over the `catch` block in case of normal termination, another — to rethrow the exception in the `catch` block. 68 | 69 | Here's the byte code for teh simplest case: 70 | 71 | ``` 72 | ALOAD 0 ; iterable 73 | INVOKEINTERFACE java/lang/Iterable.iterator ()Ljava/util/Iterator; 74 | ASTORE 2 ; tmp_iterator 75 | 76 | START_TRY: 77 | LOOP: 78 | ALOAD 2 ; tmp_iterator 79 | INVOKEINTERFACE java/util/Iterator.hasNext ()Z 80 | IFEQ FINALLY 81 | 82 | ALOAD 2 ; tmp_iterator 83 | INVOKEINTERFACE java/util/Iterator.next ()Ljava/lang/Object; 84 | ASTORE 1 ; loop_variable 85 | 86 | // loop body 87 | // ... 88 | 89 | GOTO LOOP 90 | 91 | FINALLY: 92 | ALOAD 2 ; tmp_iterator ; overhead 93 | INVOKESTATIC disposeIfNeeded (Ljava/lang/Object;)V ; overhead 94 | ; overhead 95 | GOTO AFTER_CATCH 96 | 97 | CATCH: 98 | ALOAD 2 ; tmp_iterator ; overhead 99 | INVOKESTATIC disposeIfNeeded (Ljava/lang/Object;)V ; overhead 100 | 101 | ATHROW ; overhead 102 | 103 | AFTER_CATCH: 104 | // code after the loop 105 | // ... 106 | ``` 107 | 108 | ## Compatibility concerns 109 | 110 | The code compiled with any pre-1.1 will run against the new library, but the `for`-loops there won't be disposing their iterators. This is not exactly a binary incompatibility: all code will keep working as before, but old clients for the new code will be unprepared, and won't hold their part of the deal (that is expected by the new code). 111 | 112 | > A minor and incomplete mitigation for this will be having the iterators dispose themselves when teh iteration completes normally, i.e. when `hasNext()` returns `false` for the first time. This won't help the case of an exceptions or early termination of a loop, though. 113 | 114 | If we are ready to live with this, i.e. warn the users to recompile their old code, some new concerns arise: 115 | - the recompiled code will depend on the new `kotlin-runtime`, 116 | - recompiling the old code with Kotlin 1.1 may be undesirable for setup/compiler changes reasons. 117 | 118 | This leads to thinking of adding this feature as a minimal change to Kotlin 1.0.X (under a flag, probably), and making it emit code that is tolerant to the old runtime (e.g. doesn't fail if `disposeIfNeeded()` or `Disposable` are missing). 119 | 120 | The possible options here are: 121 | - Use `Class.forName` to check for presense of `Disposable` (do nothing if it's not present), 122 | - Catch `NoSuchMethodError` around the call to `disposeIfNeeded()` and swallow it. 123 | 124 | Neither of these approaches will stop ProGuard from complaining. 125 | 126 | Both these approaches are a bit costly when applied straightforwardly: 127 | - too many instructions, 128 | - time-consuming operations (class lookup or filling the stack trace for an error). 129 | 130 | We can have fewer instructions by emitting a method that encapsulates this logic into 131 | - each module (troublesome for incremental compilation), 132 | - each file (more code). 133 | 134 | We can mitigate time costs by caching the results: having a static flag for either whether `Disposable` is present, or `disposeIfNeeded()`. 135 | 136 | ## JS costs 137 | 138 | TODO 139 | 140 | Looks like an explicit `try`/`finally` has be generated around `for`-loops. 141 | 142 | ## Arguments against this proposal 143 | 144 | - Maybe the compatibility issues are prohibitive for this feature? 145 | - Other ways of iterating, that do not use `for`-loops, won't become disposable-aware through this proposal 146 | - This is introducing a whole new concept of disposability into the ecosystem 147 | -------------------------------------------------------------------------------- /examples/channel/channel-example-1.kt: -------------------------------------------------------------------------------- 1 | package channel.example1 2 | 3 | import channel.* 4 | import delay.* 5 | 6 | // https://tour.golang.org/concurrency/1 7 | 8 | suspend fun say(s: String) { 9 | for (i in 0..4) { 10 | delay(100) 11 | println(s) 12 | } 13 | } 14 | 15 | fun main(args: Array) = mainBlocking { 16 | go { say("world") } 17 | say("hello") 18 | } 19 | 20 | -------------------------------------------------------------------------------- /examples/channel/channel-example-2.kt: -------------------------------------------------------------------------------- 1 | package channel.example2 2 | 3 | import channel.* 4 | 5 | suspend fun sum(s: List, c: SendChannel) { 6 | var sum = 0 7 | for (v in s) { 8 | sum += v 9 | } 10 | c.send(sum) 11 | } 12 | 13 | fun main(args: Array) = mainBlocking { 14 | val s = listOf(7, 2, 8, -9, 4, 0) 15 | val c = Channel() 16 | go { sum(s.subList(s.size / 2, s.size), c) } 17 | go { sum(s.subList(0, s.size / 2), c) } 18 | val x = c.receive() 19 | val y = c.receive() 20 | println("$x $y ${x + y}") 21 | } -------------------------------------------------------------------------------- /examples/channel/channel-example-2a.kt: -------------------------------------------------------------------------------- 1 | package channel.example2a 2 | 3 | import channel.* 4 | import kotlin.system.* 5 | 6 | suspend fun sum(s: List, c: SendChannel) { 7 | // simulate long-running CPU-consuming computation 8 | var sum = 0 9 | val time = measureTimeMillis { 10 | repeat(100_000_000) { 11 | for (v in s) { 12 | sum += v 13 | } 14 | } 15 | c.send(sum) 16 | } 17 | println("Sum took $time ms") 18 | } 19 | 20 | fun main(args: Array) = mainBlocking { 21 | val s = listOf(7, 2, 8, -9, 4, 0) 22 | val c = Channel() 23 | go { sum(s.subList(s.size /2, s.size), c) } 24 | go { sum(s.subList(0, s.size / 2), c) } 25 | val time = measureTimeMillis { 26 | val x = c.receive() 27 | val y = c.receive() 28 | println("$x $y ${x + y}") 29 | } 30 | println("Main code took $time ms") 31 | } -------------------------------------------------------------------------------- /examples/channel/channel-example-3.kt: -------------------------------------------------------------------------------- 1 | package channel.example3 2 | 3 | import channel.* 4 | 5 | // https://tour.golang.org/concurrency/3 6 | 7 | fun main(args: Array) = mainBlocking { 8 | val c = Channel(2) 9 | c.send(1) 10 | c.send(2) 11 | println(c.receive()) 12 | println(c.receive()) 13 | } -------------------------------------------------------------------------------- /examples/channel/channel-example-4.kt: -------------------------------------------------------------------------------- 1 | package channel.example4 2 | 3 | import channel.* 4 | 5 | // https://tour.golang.org/concurrency/4 6 | 7 | suspend fun fibonacci(n: Int, c: SendChannel) { 8 | var x = 0 9 | var y = 1 10 | for (i in 0..n - 1) { 11 | c.send(x) 12 | val next = x + y 13 | x = y 14 | y = next 15 | } 16 | c.close() 17 | } 18 | 19 | fun main(args: Array) = mainBlocking { 20 | val c = Channel(2) 21 | go { fibonacci(10, c) } 22 | for (i in c) { 23 | println(i) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/channel/channel-example-5.kt: -------------------------------------------------------------------------------- 1 | package channel.example5 2 | 3 | import channel.* 4 | 5 | // https://tour.golang.org/concurrency/5 6 | 7 | suspend fun fibonacci(c: SendChannel, quit: ReceiveChannel) { 8 | var x = 0 9 | var y = 1 10 | whileSelect { 11 | c.onSend(x) { 12 | val next = x + y 13 | x = y 14 | y = next 15 | true // continue while loop 16 | } 17 | quit.onReceive { 18 | println("quit") 19 | false // break while loop 20 | } 21 | } 22 | } 23 | 24 | fun main(args: Array) = mainBlocking { 25 | val c = Channel(2) 26 | val quit = Channel(2) 27 | go { 28 | for (i in 0..9) 29 | println(c.receive()) 30 | quit.send(0) 31 | } 32 | fibonacci(c, quit) 33 | } 34 | -------------------------------------------------------------------------------- /examples/channel/channel-example-6.kt: -------------------------------------------------------------------------------- 1 | package channel.example6 2 | 3 | import channel.* 4 | import delay.* 5 | 6 | // https://tour.golang.org/concurrency/6 7 | 8 | fun main(args: Array) = mainBlocking { 9 | val tick = Time.tick(100) 10 | val boom = Time.after(500) 11 | whileSelect { 12 | tick.onReceive { 13 | println("tick.") 14 | true // continue loop 15 | } 16 | boom.onReceive { 17 | println("BOOM!") 18 | false // break loop 19 | } 20 | onDefault { 21 | println(" .") 22 | delay(50) 23 | true // continue loop 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/channel/channel-example-8.kt: -------------------------------------------------------------------------------- 1 | package channel.example8 2 | 3 | import channel.* 4 | import java.util.* 5 | 6 | // https://tour.golang.org/concurrency/7 7 | // https://tour.golang.org/concurrency/8 8 | 9 | val treeSize = 10 10 | 11 | suspend fun Tree.walk(ch: SendChannel): Unit { 12 | left?.walk(ch) 13 | ch.send(value) 14 | right?.walk(ch) 15 | } 16 | 17 | suspend fun same(t1: Tree, t2: Tree): Boolean { 18 | val c1 = Channel() 19 | val c2 = Channel() 20 | go { t1.walk(c1) } 21 | go { t2.walk(c2) } 22 | var same = true 23 | for (i in 1..treeSize) { 24 | val v1 = c1.receive() 25 | val v2 = c2.receive() 26 | if (v1 != v2) same = false 27 | } 28 | return same 29 | } 30 | 31 | fun main(args: Array) = mainBlocking { 32 | val t1 = newTree(1) 33 | val t2 = newTree(1) 34 | val t3 = newTree(2) 35 | println("t1 = $t1") 36 | println("t2 = $t2") 37 | println("t3 = $t3") 38 | println("t1 same as t2? ${same(t1, t2)}") 39 | println("t1 same as t3? ${same(t1, t3)}") 40 | } 41 | 42 | // https://github.com/golang/tour/blob/master/tree/tree.go 43 | 44 | data class Tree(val value: Int, val left: Tree? = null, val right: Tree? = null) 45 | 46 | fun Tree?.insert(v: Int): Tree { 47 | if (this == null) return Tree(v) 48 | if (v < value) 49 | return Tree(value, left.insert(v), right) 50 | else 51 | return Tree(value, left, right.insert(v)) 52 | } 53 | 54 | fun newTree(k: Int): Tree { 55 | var t: Tree? = null 56 | val list = (1..treeSize).toMutableList() 57 | Collections.shuffle(list) 58 | for (v in list) { 59 | t = t.insert(v * k) 60 | } 61 | return t!! 62 | } 63 | -------------------------------------------------------------------------------- /examples/channel/channel-example-9.kt: -------------------------------------------------------------------------------- 1 | package channel.example9 2 | 3 | import channel.* 4 | import delay.* 5 | import mutex.* 6 | 7 | // https://tour.golang.org/concurrency/9 8 | 9 | class SafeCounter { 10 | private val v = mutableMapOf() 11 | private val mux = Mutex() 12 | 13 | suspend fun inc(key: String) { 14 | mux.lock() 15 | try { v[key] = v.getOrDefault(key, 0) + 1 } 16 | finally { mux.unlock() } 17 | } 18 | 19 | suspend fun get(key: String): Int? { 20 | mux.lock() 21 | return try { v[key] } 22 | finally { mux.unlock() } 23 | } 24 | } 25 | 26 | fun main(args: Array) = mainBlocking { 27 | val c = SafeCounter() 28 | for (i in 0..999) { 29 | go { c.inc("somekey") } // 1000 concurrent coroutines 30 | } 31 | delay(1000) 32 | println("${c.get("somekey")}") 33 | } 34 | -------------------------------------------------------------------------------- /examples/channel/channel-example-boring.kt: -------------------------------------------------------------------------------- 1 | package channel.boring 2 | 3 | import channel.* 4 | import delay.* 5 | import java.util.* 6 | 7 | // https://talks.golang.org/2012/concurrency.slide#25 8 | 9 | suspend fun boring(msg: String): ReceiveChannel { // returns receive-only channel of strings 10 | val c = Channel() 11 | val rnd = Random() 12 | go { 13 | var i = 0 14 | while (true) { 15 | c.send("$msg $i") 16 | delay(rnd.nextInt(1000).toLong()) 17 | i++ 18 | } 19 | } 20 | return c // return the channel to the caller 21 | } 22 | 23 | // https://talks.golang.org/2012/concurrency.slide#26 24 | 25 | fun main(args: Array) = mainBlocking { 26 | val joe = boring("Joe") 27 | val ann = boring("Ann") 28 | for (i in 0..4) { 29 | println(joe.receive()) 30 | println(ann.receive()) 31 | } 32 | println("Your're both boring; I'm leaving.") 33 | } 34 | -------------------------------------------------------------------------------- /examples/channel/channel-example-multiplexing.kt: -------------------------------------------------------------------------------- 1 | package channel.multiplexing 2 | 3 | import channel.* 4 | import channel.boring.* 5 | 6 | // https://talks.golang.org/2012/concurrency.slide#27 7 | 8 | suspend fun fanIn(input1: ReceiveChannel, input2: ReceiveChannel): ReceiveChannel { 9 | val c = Channel() 10 | go { 11 | for (v in input1) 12 | c.send(v) 13 | } 14 | go { 15 | for (v in input2) 16 | c.send(v) 17 | } 18 | return c // return combo channel 19 | } 20 | 21 | fun main(args: Array) = mainBlocking { 22 | val c = fanIn(boring("Joe"), boring("Ann")) 23 | for (i in 0..9) { 24 | println(c.receive()) 25 | } 26 | println("Your're both boring; I'm leaving.") 27 | } 28 | -------------------------------------------------------------------------------- /examples/channel/channel-example-multiplxing2.kt: -------------------------------------------------------------------------------- 1 | package channel.multiplexing2 2 | 3 | import channel.* 4 | import channel.boring.* 5 | 6 | // https://talks.golang.org/2012/concurrency.slide#27 7 | 8 | suspend fun fanIn(input1: ReceiveChannel, input2: ReceiveChannel): ReceiveChannel { 9 | val c = Channel() 10 | go { 11 | while(true) { 12 | val s = select { 13 | input1.onReceive { it } 14 | input2.onReceive { it } 15 | } 16 | c.send(s) 17 | } 18 | } 19 | return c // return combo channel 20 | } 21 | 22 | fun main(args: Array) = mainBlocking { 23 | val c = fanIn(boring("Joe"), boring("Ann")) 24 | for (i in 0..9) { 25 | println(c.receive()) 26 | } 27 | println("Your're both boring; I'm leaving.") 28 | } 29 | -------------------------------------------------------------------------------- /examples/channel/channel.kt: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import java.util.* 4 | import java.util.concurrent.atomic.* 5 | import java.util.concurrent.locks.* 6 | import kotlin.coroutines.* 7 | 8 | interface SendChannel { 9 | suspend fun send(value: T) 10 | fun close() 11 | fun selectSend(a: SendCase): Boolean 12 | } 13 | 14 | interface ReceiveChannel { 15 | suspend fun receive(): T // throws NoSuchElementException on closed channel 16 | suspend fun receiveOrNull(): T? // returns null on closed channel 17 | fun selectReceive(a: ReceiveCase): Boolean 18 | suspend operator fun iterator(): ReceiveIterator 19 | } 20 | 21 | interface ReceiveIterator { 22 | suspend operator fun hasNext(): Boolean 23 | suspend operator fun next(): T 24 | } 25 | 26 | private const val CHANNEL_CLOSED = "Channel was closed" 27 | 28 | private val channelCounter = AtomicLong() // number channels for debugging 29 | 30 | class Channel(val capacity: Int = 1) : SendChannel, ReceiveChannel { 31 | init { require(capacity >= 1) } 32 | private val number = channelCounter.incrementAndGet() // for debugging 33 | private var closed = false 34 | private val buffer = ArrayDeque(capacity) 35 | private val waiters = SentinelWaiter() 36 | 37 | private val empty: Boolean get() = buffer.isEmpty() 38 | private val full: Boolean get() = buffer.size == capacity 39 | 40 | suspend override fun send(value: T): Unit = suspendCoroutine sc@ { c -> 41 | var receiveWaiter: Waiter? = null 42 | locked { 43 | check(!closed) { CHANNEL_CLOSED } 44 | if (full) { 45 | addWaiter(SendWaiter(c, value)) 46 | return@sc // suspended 47 | } else { 48 | receiveWaiter = unlinkFirstWaiter() 49 | if (receiveWaiter == null) { 50 | buffer.add(value) 51 | } 52 | } 53 | } 54 | receiveWaiter?.resumeReceive(value) 55 | c.resume(Unit) // sent -> resume this coroutine right away 56 | } 57 | 58 | override fun selectSend(a: SendCase): Boolean { 59 | var receiveWaiter: Waiter? = null 60 | locked { 61 | if (a.selector.resolved) return true // already resolved selector, do nothing 62 | check(!closed) { CHANNEL_CLOSED } 63 | if (full) { 64 | addWaiter(a) 65 | return false // suspended 66 | } else { 67 | receiveWaiter = unlinkFirstWaiter() 68 | if (receiveWaiter == null) { 69 | buffer.add(a.value) 70 | } 71 | } 72 | a.unlink() // was resolved 73 | } 74 | receiveWaiter?.resumeReceive(a.value) 75 | a.resumeSend() // sent -> resume this coroutine right away 76 | return true 77 | } 78 | 79 | @Suppress("UNCHECKED_CAST") 80 | suspend override fun receive(): T = suspendCoroutine sc@ { c -> 81 | var sendWaiter: Waiter? = null 82 | var wasClosed = false 83 | var result: T? = null 84 | locked { 85 | if (empty) { 86 | if (closed) { 87 | wasClosed = true 88 | } else { 89 | addWaiter(ReceiveWaiter(c)) 90 | return@sc // suspended 91 | } 92 | } else { 93 | result = buffer.removeFirst() 94 | sendWaiter = unlinkFirstWaiter() 95 | if (sendWaiter != null) buffer.add(sendWaiter!!.getSendValue()) 96 | } 97 | } 98 | sendWaiter?.resumeSend() 99 | if (wasClosed) 100 | c.resumeWithException(NoSuchElementException(CHANNEL_CLOSED)) 101 | else 102 | c.resume(result as T) 103 | } 104 | 105 | suspend override fun receiveOrNull(): T? = suspendCoroutine sc@ { c -> 106 | var sendWaiter: Waiter? = null 107 | var result: T? = null 108 | locked { 109 | if (empty) { 110 | if (!closed) { 111 | addWaiter(ReceiveOrNullWaiter(c)) 112 | return@sc // suspended 113 | } 114 | } else { 115 | result = buffer.removeFirst() 116 | sendWaiter = unlinkFirstWaiter() 117 | if (sendWaiter != null) buffer.add(sendWaiter!!.getSendValue()) 118 | } 119 | } 120 | sendWaiter?.resumeSend() 121 | c.resume(result) 122 | } 123 | 124 | override fun selectReceive(a: ReceiveCase): Boolean { 125 | var sendWaiter: Waiter? = null 126 | var wasClosed = false 127 | var result: T? = null 128 | locked { 129 | if (a.selector.resolved) return true // already resolved selector, do nothing 130 | if (empty) { 131 | if (closed) { 132 | wasClosed = true 133 | } else { 134 | addWaiter(a) 135 | return false // suspended 136 | } 137 | } else { 138 | result = buffer.removeFirst() 139 | sendWaiter = unlinkFirstWaiter() 140 | if (sendWaiter != null) buffer.add(sendWaiter!!.getSendValue()) 141 | } 142 | a.unlink() // was resolved 143 | } 144 | sendWaiter?.resumeSend() 145 | if (wasClosed) 146 | a.resumeClosed() 147 | else 148 | @Suppress("UNCHECKED_CAST") 149 | a.resumeReceive(result as T) 150 | return true 151 | } 152 | 153 | suspend override fun iterator(): ReceiveIterator = ReceiveIteratorImpl() 154 | 155 | inner class ReceiveIteratorImpl: ReceiveIterator { 156 | private var computedNext = false 157 | private var hasNextValue = false 158 | private var nextValue: T? = null 159 | 160 | suspend override fun hasNext(): Boolean { 161 | if (computedNext) return hasNextValue 162 | return suspendCoroutine sc@ { c -> 163 | var sendWaiter: Waiter? = null 164 | locked { 165 | if (empty) { 166 | if (!closed) { 167 | addWaiter(IteratorHasNextWaiter(c, this)) 168 | return@sc // suspended 169 | } else 170 | setClosed() 171 | } else { 172 | setNext(buffer.removeFirst()) 173 | sendWaiter = unlinkFirstWaiter() 174 | if (sendWaiter != null) buffer.add(sendWaiter!!.getSendValue()) 175 | } 176 | } 177 | sendWaiter?.resumeSend() 178 | c.resume(hasNextValue) 179 | } 180 | } 181 | 182 | suspend override fun next(): T { 183 | // return value previous acquired by hasNext 184 | if (computedNext) { 185 | @Suppress("UNCHECKED_CAST") 186 | val result = nextValue as T 187 | computedNext = false 188 | nextValue = null 189 | return result 190 | } 191 | // do a regular receive is hasNext was not previously invoked 192 | return receive() 193 | } 194 | 195 | fun setNext(value: T) { 196 | computedNext = true 197 | hasNextValue = true 198 | nextValue = value 199 | } 200 | 201 | fun setClosed() { 202 | computedNext = true 203 | hasNextValue = false 204 | } 205 | } 206 | 207 | @Suppress("UNCHECKED_CAST") 208 | override fun close() { 209 | var killList: ArrayList>? = null 210 | locked { 211 | if (closed) return // ignore repeated close 212 | closed = true 213 | if (empty || full) { 214 | killList = arrayListOf() 215 | while (true) { 216 | killList!!.add(unlinkFirstWaiter() ?: break) 217 | } 218 | } else { 219 | check (!hasWaiters) { "Channel with butter not-full and not-empty shall not have waiters"} 220 | return // nothing to do 221 | } 222 | } 223 | for (kill in killList!!) { 224 | kill.resumeClosed() 225 | } 226 | } 227 | 228 | private val hasWaiters: Boolean get() = waiters.next != waiters 229 | 230 | private fun addWaiter(w: Waiter) { 231 | val last = waiters.prev!! 232 | w.prev = last 233 | w.next = waiters 234 | last.next = w 235 | waiters.prev = w 236 | } 237 | 238 | private fun unlinkFirstWaiter(): Waiter? { 239 | val first = waiters.next!! 240 | if (first == waiters) return null 241 | first.unlink() 242 | return first 243 | } 244 | 245 | // debugging 246 | private val waitersString: String get() { 247 | val sb = StringBuilder("[") 248 | var w = waiters.next!! 249 | while (w != waiters) { 250 | if (sb.length > 1) sb.append(", ") 251 | sb.append(w) 252 | w = w.next!! 253 | } 254 | sb.append("]") 255 | return sb.toString() 256 | } 257 | 258 | override fun toString(): String = locked { 259 | "Channel #$number closed=$closed, buffer=$buffer, waiters=$waitersString" 260 | } 261 | } 262 | 263 | // This lock is used only for a short time to manage wait lists, no user code runs under it 264 | private val lock = ReentrantLock() 265 | 266 | private inline fun locked(block: () -> R): R { 267 | lock.lock() 268 | return try { block() } finally { lock.unlock() } 269 | } 270 | 271 | sealed class Waiter { 272 | var next: Waiter? = null 273 | var prev: Waiter? = null 274 | 275 | open fun resumeReceive(value: T) { throw IllegalStateException() } 276 | open fun resumeClosed() { throw IllegalStateException() } 277 | open fun getSendValue(): T { throw IllegalStateException() } 278 | open fun resumeSend() { throw IllegalStateException() } 279 | 280 | val linked: Boolean get() = next != null 281 | 282 | open fun unlink() { unlinkOne() } 283 | 284 | fun unlinkOne() { 285 | val prev = this.prev!! 286 | val next = this.next!! 287 | prev.next = next 288 | next.prev = prev 289 | this.prev = null 290 | this.next = null 291 | } 292 | 293 | // debug 294 | override fun toString(): String = "${super.toString()} linked=$linked" 295 | } 296 | 297 | class SentinelWaiter : Waiter() { 298 | init { 299 | prev = this 300 | next = this 301 | } 302 | 303 | override fun unlink() { throw IllegalStateException() } 304 | } 305 | 306 | class SendWaiter(val c: Continuation, val value: T) : Waiter() { 307 | override fun getSendValue(): T = value 308 | override fun resumeSend() = c.resume(Unit) 309 | override fun resumeClosed() = c.resumeWithException(IllegalStateException(CHANNEL_CLOSED)) 310 | } 311 | 312 | class ReceiveWaiter(val c: Continuation) : Waiter() { 313 | override fun resumeReceive(value: T) = c.resume(value) 314 | override fun resumeClosed() = c.resumeWithException(NoSuchElementException(CHANNEL_CLOSED)) 315 | } 316 | 317 | class ReceiveOrNullWaiter(val c: Continuation) : Waiter() { 318 | override fun resumeReceive(value: T) = c.resume(value) 319 | override fun resumeClosed() = c.resume(null) 320 | } 321 | 322 | class IteratorHasNextWaiter(val c: Continuation, val it: Channel.ReceiveIteratorImpl) : Waiter() { 323 | override fun resumeReceive(value: T) { it.setNext(value); c.resume(true) } 324 | override fun resumeClosed() { it.setClosed(); c.resume(false) } 325 | } 326 | 327 | data class Selector(val c: Continuation, val cases: List>) { 328 | var resolved = false 329 | 330 | fun resolve() { 331 | resolved = true 332 | cases 333 | .asSequence() 334 | .filter { it.linked } 335 | .forEach { it.unlinkOne() } 336 | } 337 | } 338 | 339 | sealed class SelectCase : Waiter() { 340 | lateinit var selector: Selector 341 | abstract fun select(selector: Selector): Boolean 342 | 343 | override fun unlink() { 344 | selector.resolve() 345 | } 346 | } 347 | 348 | class SendCase(val c: SendChannel, val value: T, val action: () -> R) : SelectCase() { 349 | override fun getSendValue(): T = value 350 | override fun resumeSend() = selector.c.resume(action()) 351 | override fun resumeClosed() = selector.c.resumeWithException(IllegalStateException(CHANNEL_CLOSED)) 352 | override fun select(selector: Selector): Boolean = c.selectSend(this) 353 | } 354 | 355 | class ReceiveCase(val c: ReceiveChannel, val action: (T) -> R) : SelectCase() { 356 | override fun resumeReceive(value: T) = selector.c.resume(action(value)) 357 | override fun resumeClosed() = selector.c.resumeWithException(NoSuchElementException(CHANNEL_CLOSED)) 358 | override fun select(selector: Selector): Boolean = c.selectReceive(this) 359 | } 360 | 361 | class DefaultCase(val action: suspend () -> R) : SelectCase() { 362 | override fun select(selector: Selector): Boolean { 363 | locked { 364 | if (selector.resolved) return true // already resolved selector, do nothing 365 | selector.resolve() // default case resolves selector immediately 366 | } 367 | // now start action 368 | action.startCoroutine(completion = selector.c) 369 | return true 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /examples/channel/go.kt: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import context.* 4 | import run.* 5 | 6 | fun mainBlocking(block: suspend () -> Unit) = runBlocking(CommonPool, block) 7 | 8 | fun go(block: suspend () -> Unit) = CommonPool.runParallel(block) 9 | 10 | -------------------------------------------------------------------------------- /examples/channel/select.kt: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import kotlin.coroutines.* 4 | 5 | suspend inline fun select(block: SelectorBuilder.() -> Unit): R = 6 | SelectorBuilder().apply { block() }.doSelect() 7 | 8 | class SelectorBuilder { 9 | private val cases = mutableListOf>() 10 | 11 | fun SendChannel.onSend(value: T, action: () -> R) { 12 | cases.add(SendCase(this, value, action)) 13 | } 14 | 15 | fun ReceiveChannel.onReceive(action: (T) -> R) { 16 | cases.add(ReceiveCase(this, action)) 17 | } 18 | 19 | fun onDefault(action: suspend () -> R) { 20 | cases.add(DefaultCase(action)) 21 | } 22 | 23 | suspend fun doSelect(): R { 24 | require(!cases.isEmpty()) 25 | return suspendCoroutine { c -> 26 | val selector = Selector(c, cases) 27 | for (case in cases) { 28 | case.selector = selector 29 | if (case.select(selector)) break 30 | } 31 | } 32 | } 33 | } 34 | 35 | suspend fun whileSelect(block: SelectorBuilder.() -> Unit) { 36 | while(select(block)) { /*loop*/ } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /examples/channel/time.kt: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import delay.* 4 | import java.time.* 5 | 6 | object Time { 7 | fun tick(millis: Long): ReceiveChannel { 8 | val c = Channel() 9 | go { 10 | while (true) { 11 | delay(millis) 12 | c.send(Instant.now()) 13 | } 14 | } 15 | return c 16 | } 17 | 18 | fun after(millis: Long): ReceiveChannel { 19 | val c = Channel() 20 | go { 21 | delay(millis) 22 | c.send(Instant.now()) 23 | c.close() 24 | } 25 | return c 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/context/auth-example.kt: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import run.* 4 | import kotlin.coroutines.* 5 | 6 | suspend fun doSomething() { 7 | val currentUser = coroutineContext[AuthUser]?.name ?: throw SecurityException("unauthorized") 8 | println("Current user is $currentUser") 9 | } 10 | 11 | fun main(args: Array) { 12 | runBlocking(AuthUser("admin")) { 13 | doSomething() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/context/auth.kt: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import kotlin.coroutines.* 4 | 5 | class AuthUser(val name: String) : AbstractCoroutineContextElement(AuthUser) { 6 | companion object Key : CoroutineContext.Key 7 | } 8 | -------------------------------------------------------------------------------- /examples/context/pool-example.kt: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import future.* 4 | import run.* 5 | import util.* 6 | 7 | fun main(args: Array) = runBlocking(CommonPool) { 8 | // multithreaded pool 9 | val n = 4 10 | val compute = newFixedThreadPoolContext(n, "Compute") 11 | // start 4 coroutines to do some heavy computation 12 | val subs = Array(n) { i -> 13 | future(compute) { 14 | log("Starting computation #$i") 15 | Thread.sleep(1000) // simulate long running operation 16 | log("Done computation #$i") 17 | } 18 | } 19 | // await all of them 20 | subs.forEach { it.await() } 21 | log("Done all") 22 | } -------------------------------------------------------------------------------- /examples/context/pool.kt: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import run.* 4 | import java.util.concurrent.* 5 | import kotlin.coroutines.* 6 | 7 | object CommonPool : Pool(ForkJoinPool.commonPool()) 8 | 9 | open class Pool(val pool: ForkJoinPool) : AbstractCoroutineContextElement(ContinuationInterceptor), 10 | ContinuationInterceptor { 11 | override fun interceptContinuation(continuation: Continuation): Continuation = 12 | PoolContinuation(pool, continuation.context.fold(continuation) { cont, element -> 13 | if (element != this@Pool && element is ContinuationInterceptor) 14 | element.interceptContinuation(cont) else cont 15 | }) 16 | 17 | // runs new coroutine in this pool in parallel (schedule to a different thread) 18 | fun runParallel(block: suspend () -> Unit) { 19 | pool.execute { launch(this, block) } 20 | } 21 | } 22 | 23 | private class PoolContinuation( 24 | val pool: ForkJoinPool, 25 | val cont: Continuation 26 | ) : Continuation { 27 | override val context: CoroutineContext = cont.context 28 | 29 | override fun resumeWith(result: Result) { 30 | pool.execute { cont.resumeWith(result) } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/context/swing-delay-example.kt: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import future.* 4 | import util.* 5 | 6 | fun main(args: Array) { 7 | future(Swing) { 8 | log("Let's Swing.delay for 1 second") 9 | Swing.delay(1000) 10 | log("We're still in Swing EDT") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/context/swing-delay.kt: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import javax.swing.* 4 | import kotlin.coroutines.* 5 | 6 | suspend fun Swing.delay(millis: Int): Unit = suspendCoroutine { cont -> 7 | Timer(millis) { cont.resume(Unit) }.apply { 8 | isRepeats = false 9 | start() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/context/swing-example.kt: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import run.* 4 | import util.* 5 | import java.util.concurrent.* 6 | import kotlin.coroutines.* 7 | 8 | suspend fun makeRequest(): String { 9 | log("Making request...") 10 | return suspendCoroutine { c -> 11 | ForkJoinPool.commonPool().execute { 12 | c.resume("Result of the request") 13 | } 14 | } 15 | } 16 | 17 | fun display(result: String) { 18 | log("Displaying result '$result'") 19 | } 20 | 21 | fun main(args: Array) { 22 | launch(Swing) { 23 | try { 24 | // suspend while asynchronously making request 25 | val result = makeRequest() 26 | // display result in UI, here Swing context ensures that we always stay in event dispatch thread 27 | display(result) 28 | } catch (exception: Throwable) { 29 | // process exception 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/context/swing.kt: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import javax.swing.* 4 | import kotlin.coroutines.* 5 | 6 | object Swing : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { 7 | override fun interceptContinuation(continuation: Continuation): Continuation = 8 | SwingContinuation(continuation) 9 | } 10 | 11 | private class SwingContinuation(val cont: Continuation) : Continuation { 12 | override val context: CoroutineContext = cont.context 13 | 14 | override fun resumeWith(result: Result) { 15 | SwingUtilities.invokeLater { cont.resumeWith(result) } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/context/threadContext-example.kt: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import delay.* 4 | import future.* 5 | import util.* 6 | 7 | fun main(args: Array) { 8 | log("Starting MyEventThread") 9 | val context = newSingleThreadContext("MyEventThread") 10 | val f = future(context) { 11 | log("Hello, world!") 12 | val f1 = future(context) { 13 | log("f1 is sleeping") 14 | delay(1000) // sleep 1s 15 | log("f1 returns 1") 16 | 1 17 | } 18 | val f2 = future(context) { 19 | log("f2 is sleeping") 20 | delay(1000) // sleep 1s 21 | log("f2 returns 2") 22 | 2 23 | } 24 | log("I'll wait for both f1 and f2. It should take just a second!") 25 | val sum = f1.await() + f2.await() 26 | log("And the sum is $sum") 27 | } 28 | f.get() 29 | log("Terminated") 30 | } 31 | -------------------------------------------------------------------------------- /examples/context/threadContext.kt: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import java.util.concurrent.* 4 | import java.util.concurrent.atomic.* 5 | import kotlin.concurrent.* 6 | import kotlin.coroutines.* 7 | 8 | fun newFixedThreadPoolContext(nThreads: Int, name: String) = ThreadContext(nThreads, name) 9 | fun newSingleThreadContext(name: String) = ThreadContext(1, name) 10 | 11 | class ThreadContext( 12 | nThreads: Int, 13 | name: String 14 | ) : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { 15 | val threadNo = AtomicInteger() 16 | 17 | val executor: ScheduledExecutorService = Executors.newScheduledThreadPool(nThreads) { target -> 18 | thread(start = false, isDaemon = true, name = name + "-" + threadNo.incrementAndGet()) { 19 | target.run() 20 | } 21 | } 22 | 23 | override fun interceptContinuation(continuation: Continuation): Continuation = 24 | ThreadContinuation(continuation.context.fold(continuation) { cont, element -> 25 | if (element != this@ThreadContext && element is ContinuationInterceptor) 26 | element.interceptContinuation(cont) else cont 27 | }) 28 | 29 | private inner class ThreadContinuation(val cont: Continuation) : Continuation{ 30 | override val context: CoroutineContext = cont.context 31 | 32 | override fun resumeWith(result: Result) { 33 | executor.execute { cont.resumeWith(result) } 34 | } 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /examples/delay/delay-example.kt: -------------------------------------------------------------------------------- 1 | package delay 2 | 3 | import context.* 4 | import future.* 5 | import util.* 6 | 7 | fun main(args: Array) { 8 | future(Swing) { 9 | log("Let's naively sleep for 1 second") 10 | delay(1000L) 11 | log("We're still in Swing EDT!") 12 | } 13 | } -------------------------------------------------------------------------------- /examples/delay/delay.kt: -------------------------------------------------------------------------------- 1 | package delay 2 | 3 | import java.util.concurrent.* 4 | import kotlin.coroutines.* 5 | 6 | private val executor = Executors.newSingleThreadScheduledExecutor { 7 | Thread(it, "scheduler").apply { isDaemon = true } 8 | } 9 | 10 | suspend fun delay(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS): Unit = suspendCoroutine { cont -> 11 | executor.schedule({ cont.resume(Unit) }, time, unit) 12 | } 13 | -------------------------------------------------------------------------------- /examples/future/await.kt: -------------------------------------------------------------------------------- 1 | package future 2 | 3 | import java.util.concurrent.* 4 | import kotlin.coroutines.* 5 | 6 | suspend fun CompletableFuture.await(): T = 7 | suspendCoroutine { cont: Continuation -> 8 | whenComplete { result, exception -> 9 | if (exception == null) // the future has been completed normally 10 | cont.resume(result) 11 | else // the future has completed with an exception 12 | cont.resumeWithException(exception) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/future/future-example.kt: -------------------------------------------------------------------------------- 1 | package future 2 | 3 | import java.util.concurrent.* 4 | 5 | fun foo(): CompletableFuture = CompletableFuture.supplyAsync { "foo" } 6 | fun bar(v: String): CompletableFuture = CompletableFuture.supplyAsync { "bar with $v" } 7 | 8 | fun main(args: Array) { 9 | val future = future { 10 | println("start") 11 | val x = foo().await() 12 | println("got '$x'") 13 | val y = bar(x).await() 14 | println("got '$y' after '$x'") 15 | y 16 | } 17 | future.join() 18 | } 19 | 20 | -------------------------------------------------------------------------------- /examples/future/future.kt: -------------------------------------------------------------------------------- 1 | package future 2 | 3 | import context.* 4 | import java.util.concurrent.* 5 | import kotlin.coroutines.* 6 | 7 | fun future(context: CoroutineContext = CommonPool, block: suspend () -> T): CompletableFuture = 8 | CompletableFutureCoroutine(context).also { block.startCoroutine(completion = it) } 9 | 10 | class CompletableFutureCoroutine(override val context: CoroutineContext) : CompletableFuture(), Continuation { 11 | override fun resumeWith(result: Result) { 12 | result 13 | .onSuccess { complete(it) } 14 | .onFailure { completeExceptionally(it) } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/generator/generator-test1.kt: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Samples are inspired by https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/function* 4 | 5 | // Simple example 6 | 7 | fun idMaker() = generate { 8 | var index = 0 9 | while (index < 3) 10 | yield(index++) 11 | } 12 | 13 | fun main(args: Array) { 14 | val gen = idMaker() 15 | println(gen.next(Unit)) // 0 16 | println(gen.next(Unit)) // 1 17 | println(gen.next(Unit)) // 2 18 | println(gen.next(Unit)) // null 19 | } -------------------------------------------------------------------------------- /examples/generator/generator-test2.kt: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Samples are inspired by https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/function* 4 | 5 | // Example with yieldAll 6 | 7 | fun anotherGenerator(i: Int) = generate { 8 | yield(i + 1) 9 | yield(i + 2) 10 | yield(i + 3) 11 | } 12 | 13 | fun generator(i: Int) = generate { 14 | yield(i) 15 | yieldAll(anotherGenerator(i), Unit) 16 | yield(i + 10) 17 | } 18 | 19 | fun main(args: Array) { 20 | val gen = generator(10) 21 | println(gen.next(Unit)) // 10 22 | println(gen.next(Unit)) // 11 23 | println(gen.next(Unit)) // 12 24 | println(gen.next(Unit)) // 13 25 | println(gen.next(Unit)) // 20 26 | println(gen.next(Unit)) // null 27 | } -------------------------------------------------------------------------------- /examples/generator/generator-test3.kt: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Samples are inspired by https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/function* 4 | 5 | // Passing arguments into Generators 6 | 7 | fun logGenerator() = generate { 8 | println("Started with $it") 9 | println(yield(Unit)) 10 | println(yield(Unit)) 11 | println(yield(Unit)) 12 | } 13 | 14 | fun main(args: Array) { 15 | val gen = logGenerator() 16 | gen.next("start") // start 17 | gen.next("pretzel") // pretzel 18 | gen.next("california") // california 19 | gen.next("mayonnaise") // mayonnaise 20 | } -------------------------------------------------------------------------------- /examples/generator/generator.kt: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import kotlin.coroutines.* 4 | import kotlin.coroutines.intrinsics.* 5 | 6 | /* 7 | ES6-style generator that can send values between coroutine and outer code in both ways. 8 | 9 | Note that in ES6-generators the first invocation of `next()` goes not accept a parameter, but 10 | just starts a coroutine until a subsequent `yield`, so to adopt it for the type-safe interface 11 | we must declare `next` to be always invoked with a parameter and make our coroutine receive the first 12 | parameter to `next` when it starts (so it is not lost). We also have to introduce an additional parameter to 13 | `yieldAll` to start a delegated generator. 14 | */ 15 | 16 | interface Generator { 17 | fun next(param: R): T? // returns `null` when generator is over 18 | } 19 | 20 | @RestrictsSuspension 21 | interface GeneratorBuilder { 22 | suspend fun yield(value: T): R 23 | suspend fun yieldAll(generator: Generator, param: R) 24 | } 25 | 26 | fun generate(block: suspend GeneratorBuilder.(R) -> Unit): Generator { 27 | val coroutine = GeneratorCoroutine() 28 | val initial: suspend (R) -> Unit = { result -> block(coroutine, result) } 29 | coroutine.nextStep = { param -> initial.startCoroutine(param, coroutine) } 30 | return coroutine 31 | } 32 | 33 | // Generator coroutine implementation class 34 | internal class GeneratorCoroutine: Generator, GeneratorBuilder, Continuation { 35 | lateinit var nextStep: (R) -> Unit 36 | private var lastValue: T? = null 37 | private var lastException: Throwable? = null 38 | 39 | // Generator implementation 40 | 41 | override fun next(param: R): T? { 42 | nextStep(param) 43 | lastException?.let { throw it } 44 | return lastValue 45 | } 46 | 47 | // GeneratorBuilder implementation 48 | 49 | override suspend fun yield(value: T): R = suspendCoroutineUninterceptedOrReturn { cont -> 50 | lastValue = value 51 | nextStep = { param -> cont.resume(param) } 52 | COROUTINE_SUSPENDED 53 | } 54 | 55 | override suspend fun yieldAll(generator: Generator, param: R): Unit = suspendCoroutineUninterceptedOrReturn sc@ { cont -> 56 | lastValue = generator.next(param) 57 | if (lastValue == null) return@sc Unit // delegated coroutine does not generate anything -- resume 58 | nextStep = { param -> 59 | lastValue = generator.next(param) 60 | if (lastValue == null) cont.resume(Unit) // resume when delegate is over 61 | } 62 | COROUTINE_SUSPENDED 63 | } 64 | 65 | // Continuation implementation 66 | 67 | override val context: CoroutineContext get() = EmptyCoroutineContext 68 | 69 | override fun resumeWith(result: Result) { 70 | result 71 | .onSuccess { lastValue = null } 72 | .onFailure { lastException = it } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /examples/io/io-example.kt: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import context.* 4 | import run.* 5 | import util.* 6 | import java.nio.* 7 | import java.nio.channels.* 8 | import java.nio.file.* 9 | 10 | fun main(args: Array) { 11 | launch(Swing) { 12 | val fileName = "examples/io/io.kt" 13 | log("Asynchronously loading file \"$fileName\" ...") 14 | val channel = AsynchronousFileChannel.open(Paths.get(fileName)) 15 | try { 16 | val buf = ByteBuffer.allocate(4096) 17 | val bytesRead = channel.aRead(buf) 18 | log("Read $bytesRead bytes starting with \"${String(buf.array().copyOf(10))}\"") 19 | } finally { 20 | channel.close() 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/io/io.kt: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import java.nio.* 4 | import java.nio.channels.* 5 | import kotlin.coroutines.* 6 | 7 | suspend fun AsynchronousFileChannel.aRead(buf: ByteBuffer): Int = 8 | suspendCoroutine { cont -> 9 | read(buf, 0L, Unit, object : CompletionHandler { 10 | override fun completed(bytesRead: Int, attachment: Unit) { 11 | cont.resume(bytesRead) 12 | } 13 | 14 | override fun failed(exception: Throwable, attachment: Unit) { 15 | cont.resumeWithException(exception) 16 | } 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /examples/mutex/mutex.kt: -------------------------------------------------------------------------------- 1 | package mutex 2 | 3 | import java.util.concurrent.* 4 | import java.util.concurrent.atomic.* 5 | import kotlin.coroutines.* 6 | import kotlin.coroutines.intrinsics.* 7 | 8 | class Mutex { 9 | /* 10 | Note: this is a non-optimized implementation designed for understandability, so it just 11 | uses AtomicInteger and ConcurrentLinkedQueue instead of of embedding these data structures right here 12 | to optimize object count per mutex. 13 | */ 14 | 15 | // -1 == unlocked, >= 0 -> number of active waiters 16 | private val state = AtomicInteger(-1) 17 | // can have more waiters than registered in state (we add waiter first) 18 | private val waiters = ConcurrentLinkedQueue() 19 | 20 | suspend fun lock() { 21 | // fast path -- try lock uncontended 22 | if (state.compareAndSet(-1, 0)) return 23 | // slow path -- other cases 24 | return suspendCoroutineUninterceptedOrReturn sc@ { uc -> 25 | // tentatively add a waiter before locking (and we can get resumed because of that!) 26 | val waiter = Waiter(uc.intercepted()) 27 | waiters.add(waiter) 28 | loop@ while (true) { // lock-free loop on state 29 | val curState = state.get() 30 | if (curState == -1) { 31 | if (state.compareAndSet(-1, 0)) { 32 | // Locked successfully this time, there were no _other_ waiter. 33 | // For simplicity, we don't attempt to unlink the Waiter object from the queue, 34 | // but mark ourselves as already resumed in queue (retrieveWaiter will skip marked entries). 35 | waiter.resumed = true 36 | return@sc Unit // don't suspend, but continue execution with lock 37 | 38 | } 39 | } else { // state >= 0 -- already locked --> increase waiters count and sleep peacefully until resumed 40 | check(curState >= 0) 41 | if (state.compareAndSet(curState, curState + 1)) { 42 | break@loop 43 | } 44 | } 45 | } 46 | COROUTINE_SUSPENDED // suspend 47 | } 48 | } 49 | 50 | fun unlock() { 51 | while (true) { // lock-free loop on state 52 | // see if can unlock 53 | val curState = state.get() 54 | if (curState == 0) { 55 | // cannot have any waiters in this state, because we are holding a mutex and only mutex-holder 56 | // can reduce the number of waiters 57 | if (state.compareAndSet(0, -1)) 58 | return // successfully unlocked, no waiters were there to resume 59 | } else { 60 | check(curState >= 1) 61 | // now decrease waiters count and resume waiter 62 | if (state.compareAndSet(curState, curState - 1)) { 63 | // must have a waiter!! 64 | retrieveWaiter()!!.c.resume(Unit) 65 | return 66 | } 67 | } 68 | } 69 | } 70 | 71 | private fun retrieveWaiter(): Waiter? { 72 | while (true) { 73 | val waiter = waiters.poll() ?: return null 74 | // see if this is an _actual_ waiter (not a left-over that had actually acquired the lock in the slow path) 75 | if (!waiter.resumed) 76 | return waiter 77 | // otherwise it is an artifact, just look for the next one 78 | } 79 | } 80 | 81 | private class Waiter(val c: Continuation) { 82 | var resumed = false 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /examples/run/launch.kt: -------------------------------------------------------------------------------- 1 | package run 2 | 3 | import kotlin.coroutines.* 4 | 5 | fun launch(context: CoroutineContext = EmptyCoroutineContext, block: suspend () -> Unit) = 6 | block.startCoroutine(Continuation(context) { result -> 7 | result.onFailure { exception -> 8 | val currentThread = Thread.currentThread() 9 | currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception) 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /examples/run/runBlocking.kt: -------------------------------------------------------------------------------- 1 | package run 2 | 3 | import java.util.concurrent.locks.* 4 | import kotlin.coroutines.* 5 | 6 | fun runBlocking(context: CoroutineContext, block: suspend () -> T): T = 7 | BlockingCoroutine(context).also { block.startCoroutine(it) }.getValue() 8 | 9 | private class BlockingCoroutine(override val context: CoroutineContext) : Continuation { 10 | private val lock = ReentrantLock() 11 | private val done = lock.newCondition() 12 | private var result: Result? = null 13 | 14 | private inline fun locked(block: () -> T): T { 15 | lock.lock() 16 | return try { 17 | block() 18 | } finally { 19 | lock.unlock() 20 | } 21 | } 22 | 23 | private inline fun loop(block: () -> Unit): Nothing { 24 | while (true) { 25 | block() 26 | } 27 | } 28 | 29 | override fun resumeWith(result: Result) = locked { 30 | this.result = result 31 | done.signal() 32 | } 33 | 34 | fun getValue(): T = locked { 35 | loop { 36 | val result = this.result 37 | if (result == null) { 38 | done.awaitUninterruptibly() 39 | } else { 40 | return@locked result.getOrThrow() 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/sequence/fibonacci.kt: -------------------------------------------------------------------------------- 1 | package sequence 2 | 3 | // inferred type is Sequence 4 | val fibonacci = sequence { 5 | yield(1) // first Fibonacci number 6 | var cur = 1 7 | var next = 1 8 | while (true) { 9 | yield(next) // next Fibonacci number 10 | val tmp = cur + next 11 | cur = next 12 | next = tmp 13 | } 14 | } 15 | 16 | fun main(args: Array) { 17 | println(fibonacci.take(10).joinToString()) 18 | } 19 | -------------------------------------------------------------------------------- /examples/sequence/optimized/sequenceOptimized-test.kt: -------------------------------------------------------------------------------- 1 | package sequence.optimized 2 | 3 | val fibonacci: Sequence = sequence { 4 | yield(1) // first Fibonacci number 5 | var cur = 1 6 | var next = 1 7 | while (true) { 8 | yield(next) // next Fibonacci number 9 | val tmp = cur + next 10 | cur = next 11 | next = tmp 12 | } 13 | } 14 | 15 | fun main(args: Array) { 16 | println(fibonacci) 17 | println(fibonacci.take(10).joinToString()) 18 | } 19 | -------------------------------------------------------------------------------- /examples/sequence/optimized/sequenceOptimized.kt: -------------------------------------------------------------------------------- 1 | package sequence.optimized 2 | 3 | import sequence.* 4 | import kotlin.coroutines.* 5 | import kotlin.coroutines.intrinsics.* 6 | 7 | fun sequence(block: suspend SequenceScope.() -> Unit): Sequence = Sequence { 8 | SequenceCoroutine().apply { 9 | nextStep = block.createCoroutineUnintercepted(receiver = this, completion = this) 10 | } 11 | } 12 | 13 | class SequenceCoroutine: AbstractIterator(), SequenceScope, Continuation { 14 | lateinit var nextStep: Continuation 15 | 16 | // AbstractIterator implementation 17 | override fun computeNext() { nextStep.resume(Unit) } 18 | 19 | // Completion continuation implementation 20 | override val context: CoroutineContext get() = EmptyCoroutineContext 21 | 22 | override fun resumeWith(result: Result) { 23 | result.getOrThrow() 24 | done() 25 | } 26 | 27 | // Generator implementation 28 | override suspend fun yield(value: T) { 29 | setNext(value) 30 | return suspendCoroutineUninterceptedOrReturn { cont -> 31 | nextStep = cont 32 | COROUTINE_SUSPENDED 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/sequence/sequence.kt: -------------------------------------------------------------------------------- 1 | package sequence 2 | 3 | import kotlin.coroutines.* 4 | import kotlin.experimental.* 5 | 6 | @RestrictsSuspension 7 | interface SequenceScope { 8 | suspend fun yield(value: T) 9 | } 10 | 11 | @UseExperimental(ExperimentalTypeInference::class) 12 | fun sequence(@BuilderInference block: suspend SequenceScope.() -> Unit): Sequence = Sequence { 13 | SequenceCoroutine().apply { 14 | nextStep = block.createCoroutine(receiver = this, completion = this) 15 | } 16 | } 17 | 18 | private class SequenceCoroutine: AbstractIterator(), SequenceScope, Continuation { 19 | lateinit var nextStep: Continuation 20 | 21 | // AbstractIterator implementation 22 | override fun computeNext() { nextStep.resume(Unit) } 23 | 24 | // Completion continuation implementation 25 | override val context: CoroutineContext get() = EmptyCoroutineContext 26 | 27 | override fun resumeWith(result: Result) { 28 | result.getOrThrow() // bail out on error 29 | done() 30 | } 31 | 32 | // Generator implementation 33 | override suspend fun yield(value: T) { 34 | setNext(value) 35 | return suspendCoroutine { cont -> nextStep = cont } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/sequence/sequenceOfLines.kt: -------------------------------------------------------------------------------- 1 | package sequence 2 | 3 | import java.io.* 4 | 5 | fun sequenceOfLines(fileName: String) = sequence { 6 | BufferedReader(FileReader(fileName)).use { 7 | while (true) { 8 | yield(it.readLine() ?: break) 9 | } 10 | } 11 | } 12 | 13 | fun main(args: Array) { 14 | sequenceOfLines("examples/sequence/sequenceOfLines.kt") 15 | .forEach(::println) 16 | } -------------------------------------------------------------------------------- /examples/suspendingSequence/suspendingSequence-example.kt: -------------------------------------------------------------------------------- 1 | package suspendingSequence 2 | 3 | import context.* 4 | import delay.* 5 | import run.* 6 | import util.* 7 | import java.util.* 8 | 9 | fun main(args: Array) { 10 | val context = newSingleThreadContext("MyThread") 11 | runBlocking(context) { 12 | // asynchronously generate a number every 500 ms 13 | val seq = suspendingSequence(context) { 14 | log("Starting generator") 15 | for (i in 1..10) { 16 | log("Generator yields $i") 17 | yield(i) 18 | val generatorSleep = 500L 19 | log("Generator goes to sleep for $generatorSleep ms") 20 | delay(generatorSleep) 21 | } 22 | log("Generator is done") 23 | } 24 | // simulate async work by sleeping randomly 25 | val random = Random() 26 | // consume asynchronous sequence with a regular for loop 27 | for (value in seq) { 28 | log("Consumer got value = $value") 29 | val consumerSleep = random.nextInt(1000).toLong() 30 | log("Consumer goes to sleep for $consumerSleep ms") 31 | delay(consumerSleep) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/suspendingSequence/suspendingSequence.kt: -------------------------------------------------------------------------------- 1 | package suspendingSequence 2 | 3 | import kotlin.coroutines.* 4 | import kotlin.experimental.* 5 | 6 | interface SuspendingSequenceScope { 7 | suspend fun yield(value: T) 8 | } 9 | 10 | interface SuspendingSequence { 11 | operator fun iterator(): SuspendingIterator 12 | } 13 | 14 | interface SuspendingIterator { 15 | suspend operator fun hasNext(): Boolean 16 | suspend operator fun next(): T 17 | } 18 | 19 | @UseExperimental(ExperimentalTypeInference::class) 20 | fun suspendingSequence( 21 | context: CoroutineContext = EmptyCoroutineContext, 22 | @BuilderInference block: suspend SuspendingSequenceScope.() -> Unit 23 | ): SuspendingSequence = object : SuspendingSequence { 24 | override fun iterator(): SuspendingIterator = suspendingIterator(context, block) 25 | } 26 | 27 | fun suspendingIterator( 28 | context: CoroutineContext = EmptyCoroutineContext, 29 | block: suspend SuspendingSequenceScope.() -> Unit 30 | ): SuspendingIterator = 31 | SuspendingIteratorCoroutine(context).apply { 32 | nextStep = block.createCoroutine(receiver = this, completion = this) 33 | } 34 | 35 | class SuspendingIteratorCoroutine( 36 | override val context: CoroutineContext 37 | ) : SuspendingIterator, SuspendingSequenceScope, Continuation { 38 | enum class State { INITIAL, COMPUTING_HAS_NEXT, COMPUTING_NEXT, COMPUTED, DONE } 39 | 40 | var state: State = State.INITIAL 41 | var nextValue: T? = null 42 | var nextStep: Continuation? = null // null when sequence complete 43 | 44 | // if (state == COMPUTING_NEXT) computeContinuation is Continuation 45 | // if (state == COMPUTING_HAS_NEXT) computeContinuation is Continuation 46 | var computeContinuation: Continuation<*>? = null 47 | 48 | suspend fun computeHasNext(): Boolean = suspendCoroutine { c -> 49 | state = State.COMPUTING_HAS_NEXT 50 | computeContinuation = c 51 | nextStep!!.resume(Unit) 52 | } 53 | 54 | suspend fun computeNext(): T = suspendCoroutine { c -> 55 | state = State.COMPUTING_NEXT 56 | computeContinuation = c 57 | nextStep!!.resume(Unit) 58 | } 59 | 60 | override suspend fun hasNext(): Boolean { 61 | when (state) { 62 | State.INITIAL -> return computeHasNext() 63 | State.COMPUTED -> return true 64 | State.DONE -> return false 65 | else -> throw IllegalStateException("Recursive dependency detected -- already computing next") 66 | } 67 | } 68 | 69 | override suspend fun next(): T { 70 | when (state) { 71 | State.INITIAL -> return computeNext() 72 | State.COMPUTED -> { 73 | state = State.INITIAL 74 | @Suppress("UNCHECKED_CAST") 75 | return nextValue as T 76 | } 77 | State.DONE -> throw NoSuchElementException() 78 | else -> throw IllegalStateException("Recursive dependency detected -- already computing next") 79 | } 80 | } 81 | 82 | @Suppress("UNCHECKED_CAST") 83 | fun resumeIterator(hasNext: Boolean) { 84 | when (state) { 85 | State.COMPUTING_HAS_NEXT -> { 86 | state = State.COMPUTED 87 | (computeContinuation as Continuation).resume(hasNext) 88 | } 89 | State.COMPUTING_NEXT -> { 90 | state = State.INITIAL 91 | (computeContinuation as Continuation).resume(nextValue as T) 92 | } 93 | else -> throw IllegalStateException("Was not supposed to be computing next value. Spurious yield?") 94 | } 95 | } 96 | 97 | // Completion continuation implementation 98 | override fun resumeWith(result: Result) { 99 | nextStep = null 100 | result 101 | .onSuccess { 102 | resumeIterator(false) 103 | } 104 | .onFailure { exception -> 105 | state = State.DONE 106 | computeContinuation!!.resumeWithException(exception) 107 | } 108 | } 109 | 110 | // Generator implementation 111 | override suspend fun yield(value: T): Unit = suspendCoroutine { c -> 112 | nextValue = value 113 | nextStep = c 114 | resumeIterator(true) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /examples/util/log.kt: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import java.time.* 4 | 5 | fun log(msg: String) = println("${Instant.now()} [${Thread.currentThread().name}] $msg") -------------------------------------------------------------------------------- /kotlin-coroutines-informal.md: -------------------------------------------------------------------------------- 1 | # Kotlin Coroutines 2 | 3 | Design document for Kotlin Coroutines was moved to KEEP repository 4 | [here](https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md). 5 | --------------------------------------------------------------------------------