├── .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 |
9 |
10 |
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 |
--------------------------------------------------------------------------------