0
# Racing Operations
1
2
Arrow FX Coroutines provides racing operations that allow multiple coroutine blocks to compete, with the first successful completion providing the result while all other blocks are cancelled. This enables powerful patterns for timeouts, fallbacks, and performance optimization.
3
4
## Binary Racing
5
6
### Two-Way Race with Either Result
7
8
```kotlin { .api }
9
suspend fun <A, B> raceN(crossinline fa: suspend CoroutineScope.() -> A, crossinline fb: suspend CoroutineScope.() -> B): Either<A, B>
10
suspend fun <A, B> raceN(ctx: CoroutineContext = EmptyCoroutineContext, crossinline fa: suspend CoroutineScope.() -> A, crossinline fb: suspend CoroutineScope.() -> B): Either<A, B>
11
```
12
13
Race two operations, returning an `Either` indicating which operation won.
14
15
```kotlin
16
val result = raceN(
17
{ slowDatabaseQuery() },
18
{ fastCacheQuery() }
19
)
20
21
when (result) {
22
is Either.Left -> println("Database won: ${result.value}")
23
is Either.Right -> println("Cache won: ${result.value}")
24
}
25
```
26
27
## Ternary Racing
28
29
### Three-Way Race Result Type
30
31
```kotlin { .api }
32
sealed class Race3<out A, out B, out C> {
33
data class First<A>(val winner: A) : Race3<A, Nothing, Nothing>()
34
data class Second<B>(val winner: B) : Race3<Nothing, B, Nothing>()
35
data class Third<C>(val winner: C) : Race3<Nothing, Nothing, C>()
36
37
fun <D> fold(
38
ifFirst: (A) -> D,
39
ifSecond: (B) -> D,
40
ifThird: (C) -> D
41
): D
42
}
43
```
44
45
Result type for three-way races with pattern matching capability.
46
47
### Three-Way Race Operations
48
49
```kotlin { .api }
50
suspend fun <A, B, C> raceN(
51
crossinline fa: suspend CoroutineScope.() -> A,
52
crossinline fb: suspend CoroutineScope.() -> B,
53
crossinline fc: suspend CoroutineScope.() -> C
54
): Race3<A, B, C>
55
56
suspend fun <A, B, C> raceN(
57
ctx: CoroutineContext = EmptyCoroutineContext,
58
crossinline fa: suspend CoroutineScope.() -> A,
59
crossinline fb: suspend CoroutineScope.() -> B,
60
crossinline fc: suspend CoroutineScope.() -> C
61
): Race3<A, B, C>
62
```
63
64
Race three operations with structured result handling.
65
66
```kotlin
67
val result = raceN(
68
{ primaryService.getData() },
69
{ secondaryService.getData() },
70
{ fallbackService.getData() }
71
)
72
73
val data = result.fold(
74
ifFirst = { primary -> "Primary: $primary" },
75
ifSecond = { secondary -> "Secondary: $secondary" },
76
ifThird = { fallback -> "Fallback: $fallback" }
77
)
78
```
79
80
## Advanced Racing with RacingScope
81
82
### RacingScope Interface
83
84
```kotlin { .api }
85
interface RacingScope<in Result> : CoroutineScope {
86
fun raceOrThrow(
87
context: CoroutineContext = EmptyCoroutineContext,
88
block: suspend CoroutineScope.() -> Result
89
)
90
}
91
```
92
93
Advanced racing scope that allows building complex racing scenarios.
94
95
### Racing Scope Execution
96
97
```kotlin { .api }
98
suspend fun <A> racing(block: RacingScope<A>.() -> Unit): A
99
```
100
101
Create a racing scope where multiple operations can compete.
102
103
```kotlin
104
val winner = racing<String> {
105
raceOrThrow { "Fast operation completed" }
106
raceOrThrow {
107
delay(1000)
108
"Slow operation completed"
109
}
110
raceOrThrow {
111
delay(500)
112
"Medium operation completed"
113
}
114
}
115
```
116
117
### Racing with Exception Handling
118
119
```kotlin { .api }
120
fun <Result> RacingScope<Result>.race(
121
context: CoroutineContext = EmptyCoroutineContext,
122
condition: (Result) -> Boolean = { true },
123
block: suspend CoroutineScope.() -> Result
124
)
125
```
126
127
Race with custom exception handling and success conditions.
128
129
```kotlin
130
val result = racing<String?> {
131
race(condition = { it != null }) {
132
unreliableService.getData() // May return null
133
}
134
135
race {
136
delay(5000)
137
"Timeout fallback"
138
}
139
}
140
```
141
142
## Racing with Error Handling
143
144
### RaiseScope Integration
145
146
```kotlin { .api }
147
interface RaiseScope<in Error> : CoroutineScope, Raise<Error>
148
typealias RaiseHandler<Error> = (context: CoroutineContext, error: Error) -> Unit
149
```
150
151
Racing operations that integrate with Arrow's Raise error handling system.
152
153
### Racing with Raise Error Handling
154
155
```kotlin { .api }
156
fun <Result> RacingScope<Result>.raceOrThrow(
157
raise: RaiseHandler<Error>,
158
condition: (Result) -> Boolean = { true },
159
block: suspend RaiseScope<Error>.() -> Result
160
)
161
162
fun <Result> RacingScope<Result>.race(
163
context: CoroutineContext = EmptyCoroutineContext,
164
raise: RaiseHandler<Error>,
165
condition: (Result) -> Boolean = { true },
166
block: suspend RaiseScope<Error>.() -> Result
167
)
168
```
169
170
Race operations with structured error handling.
171
172
```kotlin
173
val result = either<ServiceError, String> {
174
racing<String> {
175
raceOrThrow(raise = { error -> raise(error) }) {
176
serviceA.getData() // Can raise ServiceError
177
}
178
179
raceOrThrow(raise = { error -> raise(error) }) {
180
serviceB.getData() // Can raise ServiceError
181
}
182
}
183
}
184
```
185
186
## Common Racing Patterns
187
188
### Timeout Pattern
189
190
```kotlin
191
suspend fun <T> withTimeout(timeoutMs: Long, operation: suspend () -> T): T? {
192
return raceN(
193
{ operation() },
194
{
195
delay(timeoutMs)
196
null
197
}
198
).fold(
199
{ result -> result },
200
{ null }
201
)
202
}
203
204
val result = withTimeout(5000) {
205
slowNetworkCall()
206
}
207
```
208
209
### Fallback Pattern
210
211
```kotlin
212
suspend fun <T> withFallback(
213
primary: suspend () -> T,
214
fallback: suspend () -> T
215
): T {
216
return raceN(
217
{ primary() },
218
{
219
delay(3000) // Wait before trying fallback
220
fallback()
221
}
222
).fold(
223
{ it },
224
{ it }
225
)
226
}
227
```
228
229
### Circuit Breaker Pattern
230
231
```kotlin
232
class CircuitBreaker<T> {
233
suspend fun executeWithFallback(
234
primary: suspend () -> T,
235
fallback: suspend () -> T
236
): T = racing {
237
raceOrThrow(condition = { isServiceHealthy() }) {
238
primary()
239
}
240
241
race {
242
delay(100) // Small delay before fallback
243
fallback()
244
}
245
}
246
}
247
```
248
249
### Multiple Service Racing
250
251
```kotlin
252
suspend fun <T> raceMultipleServices(
253
services: List<suspend () -> T>
254
): T = racing {
255
services.forEach { service ->
256
raceOrThrow { service() }
257
}
258
}
259
260
val data = raceMultipleServices(
261
listOf(
262
{ serviceA.getData() },
263
{ serviceB.getData() },
264
{ serviceC.getData() }
265
)
266
)
267
```
268
269
## Performance and Cancellation
270
271
### Cancellation Behavior
272
273
All racing operations properly handle cancellation:
274
275
- When one operation completes, all other operations are immediately cancelled
276
- Resources are properly cleaned up in cancelled operations
277
- Parent scope cancellation is propagated to all racing operations
278
279
### Resource Management in Racing
280
281
```kotlin
282
val result = racing<String> {
283
raceOrThrow {
284
resourceScope {
285
val connection = connectionResource.bind()
286
connection.query("SELECT data FROM table1")
287
}
288
}
289
290
raceOrThrow {
291
resourceScope {
292
val cache = cacheResource.bind()
293
cache.get("data_key")
294
}
295
}
296
}
297
// Losing operation's resources are automatically cleaned up
298
```
299
300
### Exception Propagation
301
302
Racing operations handle exceptions according to structured concurrency principles:
303
304
```kotlin
305
val result = try {
306
racing<String> {
307
raceOrThrow {
308
throw IllegalStateException("Service A failed")
309
}
310
311
raceOrThrow {
312
delay(100)
313
"Service B success"
314
}
315
}
316
} catch (e: IllegalStateException) {
317
"Handled failure: ${e.message}"
318
}
319
```
320
321
## Integration with Other Patterns
322
323
### Racing with Parallel Processing
324
325
```kotlin
326
val results = urls.parMap { url ->
327
raceN(
328
{ primaryClient.get(url) },
329
{
330
delay(2000)
331
fallbackClient.get(url)
332
}
333
).fold({ it }, { it })
334
}
335
```
336
337
### Racing with Resource Management
338
339
```kotlin
340
resourceScope {
341
val config = configResource.bind()
342
343
val data = racing<String> {
344
config.endpoints.forEach { endpoint ->
345
raceOrThrow {
346
httpClient.get(endpoint)
347
}
348
}
349
}
350
351
processData(data)
352
}
353
```