@@ -21,6 +21,7 @@ sealed class ExitCase {
21
21
22
22
/* *
23
23
* Runs [f] in an uncancellable manner.
24
+ * If [f] gets cancelled, it will back-pressure the cancelling operation until finished.
24
25
*
25
26
* ```kotlin:ank:playground
26
27
* import arrow.fx.coroutines.*
@@ -56,6 +57,17 @@ suspend fun <A> uncancellable(f: suspend () -> A): A =
56
57
}
57
58
}
58
59
60
+ /* *
61
+ * Registers an [onCancel] handler after [fa].
62
+ * [onCancel] is guaranteed to be called in case of cancellation, otherwise it's ignored.
63
+ *
64
+ * Useful for wiring cancellation tokens between fibers, building inter-op with other effect systems or testing.
65
+ *
66
+ * @param fa program that you want to register handler on
67
+ * @param onCancel handler to run when [fa] gets cancelled.
68
+ * @see guarantee for registering a handler that is guaranteed to always run.
69
+ * @see guaranteeCase for registering a handler that executes for any [ExitCase].
70
+ */
59
71
suspend fun <A > onCancel (
60
72
fa : suspend () -> A ,
61
73
onCancel : suspend () -> Unit
@@ -66,22 +78,149 @@ suspend fun <A> onCancel(
66
78
}
67
79
}
68
80
81
+ /* *
82
+ * Guarantees execution of a given [finalizer] after [fa] regardless of success, error or cancellation.
83
+ *
84
+ * As best practice, it's not a good idea to release resources via [guarantee].
85
+ * since [guarantee] doesn't properly model acquiring, using and releasing resources.
86
+ * It only models scheduling of a finalizer after a given suspending program,
87
+ * so you should prefer [Resource] or [bracket] which captures acquiring,
88
+ * using and releasing into 3 separate steps to ensure resource safety.
89
+ *
90
+ * @param fa program that you want to register handler on
91
+ * @param finalizer handler to run after [fa].
92
+ * @see guaranteeCase for registering a handler that tracks the [ExitCase] of [fa].
93
+ */
69
94
suspend fun <A > guarantee (
70
95
fa : suspend () -> A ,
71
- release : suspend () -> Unit
72
- ): A = guaranteeCase(fa) { release .invoke() }
96
+ finalizer : suspend () -> Unit
97
+ ): A = guaranteeCase(fa) { finalizer .invoke() }
73
98
99
+ /* *
100
+ * Guarantees execution of a given [finalizer] after [fa] regardless of success, error or cancellation., allowing
101
+ * for differentiating between exit conditions with to the [ExitCase] argument of the finalizer.
102
+ *
103
+ * As best practice, it's not a good idea to release resources via [guaranteeCase].
104
+ * since [guaranteeCase] doesn't properly model acquiring, using and releasing resources.
105
+ * It only models scheduling of a finalizer after a given suspending program,
106
+ * so you should prefer [Resource] or [bracketCase] which captures acquiring,
107
+ * using and releasing into 3 separate steps to ensure resource safety.
108
+ *
109
+ * @param fa program that you want to register handler on
110
+ * @param finalizer handler to run after [fa].
111
+ * @see guarantee for registering a handler that ignores the [ExitCase] of [fa].
112
+ */
74
113
suspend fun <A > guaranteeCase (
75
114
fa : suspend () -> A ,
76
- release : suspend (ExitCase ) -> Unit
77
- ): A = bracketCase({ Unit }, { fa.invoke() }, { _, ex -> release (ex) })
115
+ finalizer : suspend (ExitCase ) -> Unit
116
+ ): A = bracketCase({ Unit }, { fa.invoke() }, { _, ex -> finalizer (ex) })
78
117
118
+ /* *
119
+ * Meant for specifying tasks with safe resource acquisition and release in the face of errors and interruption.
120
+ * It would be the equivalent of an async capable `try/catch/finally` statements in mainstream imperative languages for resource
121
+ * acquisition and release.
122
+ *
123
+ * @param acquire the action to acquire the resource
124
+ *
125
+ * @param use is the action to consume the resource and produce a result.
126
+ * Once the resulting suspend program terminates, either successfully, error or disposed,
127
+ * the [release] function will run to clean up the resources.
128
+ *
129
+ * @param release is the action that's supposed to release the allocated resource after `use` is done, irregardless
130
+ * of its exit condition.
131
+ *
132
+ * ```kotlin:ank:playground
133
+ * import arrow.fx.coroutines.*
134
+ *
135
+ * class File(url: String) {
136
+ * fun open(): File = this
137
+ * fun close(): Unit {}
138
+ * override fun toString(): String = "This file contains some interesting content!"
139
+ * }
140
+ *
141
+ * suspend fun openFile(uri: String): File = File(uri).open()
142
+ * suspend fun closeFile(file: File): Unit = file.close()
143
+ * suspend fun fileToString(file: File): String = file.toString()
144
+ *
145
+ * suspend fun main(): Unit {
146
+ * //sampleStart
147
+ * val res = bracket(
148
+ * acquire = { openFile("data.json") },
149
+ * use = { file -> fileToString(file) },
150
+ * release = { file: File -> closeFile(file) }
151
+ * )
152
+ * //sampleEnd
153
+ * println(res)
154
+ * }
155
+ * ```
156
+ */
79
157
suspend fun <A , B > bracket (
80
158
acquire : suspend () -> A ,
81
159
use : suspend (A ) -> B ,
82
160
release : suspend (A ) -> Unit
83
161
): B = bracketCase(acquire, use, { a, _ -> release(a) })
84
162
163
+ /* *
164
+ * A way to safely acquire a resource and release in the face of errors and cancellation.
165
+ * It uses [ExitCase] to distinguish between different exit cases when releasing the acquired resource.
166
+ *
167
+ * [bracketCase] exists out of a three stages:
168
+ * 1. acquisition
169
+ * 2. consumption
170
+ * 3. releasing
171
+ *
172
+ * 1. Resource acquisition is **NON CANCELLABLE**.
173
+ * If resource acquisition fails, meaning no resource was actually successfully acquired then we short-circuit the effect.
174
+ * Reason being, we cannot [release] what we did not `acquire` first. Same reason we cannot call [use].
175
+ * If it is successful we pass the result to stage 2 [use].
176
+ *
177
+ * 2. Resource consumption is like any other `suspend` effect. The key difference here is that it's wired in such a way that
178
+ * [release] **will always** be called either on [ExitCase.Cancelled], [ExitCase.Failure] or [ExitCase.Completed].
179
+ * If it failed than the resulting [suspend] from [bracketCase] will be the error, otherwise the result of [use].
180
+ *
181
+ * 3. Resource releasing is **NON CANCELLABLE**, otherwise it could result in leaks.
182
+ * In the case it throws the resulting [suspend] will be either the error or a composed error if one occurred in the [use] stage.
183
+ *
184
+ * @param acquire the action to acquire the resource
185
+ *
186
+ * @param use is the action to consume the resource and produce a result.
187
+ * Once the resulting suspend program terminates, either successfully, error or disposed,
188
+ * the [release] function will run to clean up the resources.
189
+ *
190
+ * @param release the allocated resource after [use] terminates.
191
+ *
192
+ * ```kotlin:ank:playground
193
+ * import arrow.fx.coroutines.*
194
+ *
195
+ * class File(url: String) {
196
+ * fun open(): File = this
197
+ * fun close(): Unit {}
198
+ * }
199
+ *
200
+ * suspend fun File.content(): String =
201
+ * "This file contains some interesting content!"
202
+ * suspend fun openFile(uri: String): File = File(uri).open()
203
+ * suspend fun closeFile(file: File): Unit = file.close()
204
+ *
205
+ * suspend fun main(): Unit {
206
+ * //sampleStart
207
+ * val res = bracketCase(
208
+ * acquire = { openFile("data.json") },
209
+ * use = { file -> file.content() },
210
+ * release = { file, exitCase ->
211
+ * when (exitCase) {
212
+ * is ExitCase.Completed -> println("File closed with $exitCase")
213
+ * is ExitCase.Cancelled -> println("Program cancelled with $exitCase")
214
+ * is ExitCase.Failure -> println("Program failed with $exitCase")
215
+ * }
216
+ * closeFile(file)
217
+ * }
218
+ * )
219
+ * //sampleEnd
220
+ * println(res)
221
+ * }
222
+ * ```
223
+ */
85
224
suspend fun <A , B > bracketCase (
86
225
acquire : suspend () -> A ,
87
226
use : suspend (A ) -> B ,
0 commit comments