@@ -42,6 +42,22 @@ interface ContributesToRoomLifecycle : EmitsDiscontinuities, HandlesDiscontinuit
42
42
val detachmentErrorCode: ErrorCodes
43
43
}
44
44
45
+ abstract class ContributesToRoomLifecycleImpl : ContributesToRoomLifecycle {
46
+
47
+ private val discontinuityEmitter = DiscontinuityEmitter ()
48
+
49
+ override fun onDiscontinuity (listener : EmitsDiscontinuities .Listener ): Subscription {
50
+ discontinuityEmitter.on(listener)
51
+ return Subscription {
52
+ discontinuityEmitter.off(listener)
53
+ }
54
+ }
55
+
56
+ override fun discontinuityDetected (reason : ErrorInfo ? ) {
57
+ discontinuityEmitter.emit(" discontinuity" , reason)
58
+ }
59
+ }
60
+
45
61
/* *
46
62
* This interface represents a feature that contributes to the room lifecycle and
47
63
* exposes its channel directly. Objects of this type are created by awaiting the
@@ -78,27 +94,29 @@ interface RoomAttachmentResult : NewRoomStatus {
78
94
}
79
95
80
96
class DefaultRoomAttachmentResult : RoomAttachmentResult {
81
- internal var _failedFeature : ResolvedContributor ? = null
82
- internal var _status : RoomLifecycle = RoomLifecycle . Attached
83
- internal var _error : ErrorInfo ? = null
97
+ internal var statusField : RoomLifecycle = RoomLifecycle . Attached
98
+ override val status : RoomLifecycle
99
+ get() = statusField
84
100
101
+ internal var failedFeatureField: ResolvedContributor ? = null
85
102
override val failedFeature: ResolvedContributor ?
86
- get() = _failedFeature
103
+ get() = failedFeatureField
87
104
88
- override val exception: AblyException
89
- get() = AblyException .fromErrorInfo(
90
- _error
91
- ? : ErrorInfo (
92
- " unknown error in attach for ${failedFeature?.contributor?.featureName} feature" ,
93
- 500 , ErrorCodes .RoomLifecycleError .errorCode,
94
- ),
95
- )
105
+ internal var errorField: ErrorInfo ? = null
106
+ override val error: ErrorInfo ?
107
+ get() = errorField
96
108
97
- override val status: RoomLifecycle
98
- get() = _status
109
+ internal var throwable: Throwable ? = null
99
110
100
- override val error: ErrorInfo ?
101
- get() = _error
111
+ override val exception: AblyException
112
+ get() {
113
+ val errorInfo = errorField
114
+ ? : ErrorInfo (" unknown error in attach" , HttpStatusCodes .InternalServerError , ErrorCodes .RoomLifecycleError .errorCode)
115
+ throwable?.let {
116
+ return AblyException .fromErrorInfo(throwable, errorInfo)
117
+ }
118
+ return AblyException .fromErrorInfo(errorInfo)
119
+ }
102
120
}
103
121
104
122
/* *
@@ -153,6 +171,11 @@ class RoomLifecycleManager
153
171
*/
154
172
private val _firstAttachesCompleted = mutableMapOf<ResolvedContributor , Boolean >()
155
173
174
+ /* *
175
+ * Retry duration in milliseconds, used by internal doRetry and runDownChannelsOnFailedAttach methods
176
+ */
177
+ private val _retryDurationInMs : Long = 250
178
+
156
179
init {
157
180
if (_status .current != RoomLifecycle .Attached ) {
158
181
_operationInProgress = true
@@ -183,6 +206,7 @@ class RoomLifecycleManager
183
206
* @param contributor The contributor that has entered a suspended state.
184
207
* @returns Returns when the room is attached, or the room enters a failed state.
185
208
*/
209
+ @SuppressWarnings(" CognitiveComplexMethod" )
186
210
private suspend fun doRetry (contributor : ResolvedContributor ) {
187
211
// Handle the channel wind-down for other channels
188
212
var result = kotlin.runCatching { doChannelWindDown(contributor) }
@@ -191,7 +215,7 @@ class RoomLifecycleManager
191
215
if (this ._status .current == = RoomLifecycle .Failed ) {
192
216
error(" room is in a failed state" )
193
217
}
194
- delay(250 )
218
+ delay(_retryDurationInMs )
195
219
result = kotlin.runCatching { doChannelWindDown(contributor) }
196
220
}
197
221
@@ -214,7 +238,11 @@ class RoomLifecycleManager
214
238
val failedFeature = attachmentResult.failedFeature
215
239
if (failedFeature == null ) {
216
240
AblyException .fromErrorInfo(
217
- ErrorInfo (" no failed feature in doRetry" , 500 , ErrorCodes .RoomLifecycleError .errorCode),
241
+ ErrorInfo (
242
+ " no failed feature in doRetry" ,
243
+ HttpStatusCodes .InternalServerError ,
244
+ ErrorCodes .RoomLifecycleError .errorCode,
245
+ ),
218
246
)
219
247
}
220
248
// No need to catch errors, rather they should propagate to caller method
@@ -248,7 +276,7 @@ class RoomLifecycleManager
248
276
contributor.channel.once(ChannelState .failed) {
249
277
val exception = AblyException .fromErrorInfo(
250
278
it.reason
251
- ? : ErrorInfo (" unknown error in _doRetry" , 500 , ErrorCodes .RoomLifecycleError .errorCode),
279
+ ? : ErrorInfo (" unknown error in _doRetry" , HttpStatusCodes . InternalServerError , ErrorCodes .RoomLifecycleError .errorCode),
252
280
)
253
281
continuation.resumeWithException(exception)
254
282
}
@@ -263,6 +291,7 @@ class RoomLifecycleManager
263
291
* If a channel enters the failed state, we reject and then begin to wind down the other channels.
264
292
* Spec: CHA-RL1
265
293
*/
294
+ @SuppressWarnings(" ThrowsCount" )
266
295
internal suspend fun attach () {
267
296
val deferredAttach = atomicCoroutineScope.async(LifecycleOperationPrecedence .AttachOrDetach .priority) { // CHA-RL1d
268
297
when (_status .current) {
@@ -271,15 +300,15 @@ class RoomLifecycleManager
271
300
throw AblyException .fromErrorInfo(
272
301
ErrorInfo (
273
302
" unable to attach room; room is releasing" ,
274
- 500 ,
303
+ HttpStatusCodes . InternalServerError ,
275
304
ErrorCodes .RoomIsReleasing .errorCode,
276
305
),
277
306
)
278
307
RoomLifecycle .Released -> // CHA-RL1c
279
308
throw AblyException .fromErrorInfo(
280
309
ErrorInfo (
281
310
" unable to attach room; room is released" ,
282
- 500 ,
311
+ HttpStatusCodes . InternalServerError ,
283
312
ErrorCodes .RoomIsReleased .errorCode,
284
313
),
285
314
)
@@ -306,7 +335,11 @@ class RoomLifecycleManager
306
335
if (attachResult.status == = RoomLifecycle .Suspended ) {
307
336
if (attachResult.failedFeature == null ) {
308
337
AblyException .fromErrorInfo(
309
- ErrorInfo (" no failed feature in attach" , 500 , ErrorCodes .RoomLifecycleError .errorCode),
338
+ ErrorInfo (
339
+ " no failed feature in attach" ,
340
+ HttpStatusCodes .InternalServerError ,
341
+ ErrorCodes .RoomLifecycleError .errorCode,
342
+ ),
310
343
)
311
344
}
312
345
attachResult.failedFeature?.let {
@@ -337,29 +370,26 @@ class RoomLifecycleManager
337
370
feature.channel.attachCoroutine()
338
371
_firstAttachesCompleted [feature] = true
339
372
} catch (ex: Throwable ) { // CHA-RL1h - handle channel attach failure
340
- attachResult._failedFeature = feature
341
- attachResult._error = ErrorInfo (
373
+ attachResult.throwable = ex
374
+ attachResult.failedFeatureField = feature
375
+ attachResult.errorField = ErrorInfo (
342
376
" failed to attach ${feature.contributor.featureName} feature${feature.channel.errorMessage} " ,
343
- 500 ,
377
+ HttpStatusCodes . InternalServerError ,
344
378
feature.contributor.attachmentErrorCode.errorCode,
345
379
)
346
380
347
381
// The current feature should be in one of two states, it will be either suspended or failed
348
382
// If it's in suspended, we wind down the other channels and wait for the reattach
349
383
// If it's failed, we can fail the entire room
350
384
when (feature.channel.state) {
351
- ChannelState .suspended -> {
352
- attachResult._status = RoomLifecycle .Suspended
353
- }
354
- ChannelState .failed -> {
355
- attachResult._status = RoomLifecycle .Failed
356
- }
385
+ ChannelState .suspended -> attachResult.statusField = RoomLifecycle .Suspended
386
+ ChannelState .failed -> attachResult.statusField = RoomLifecycle .Failed
357
387
else -> {
358
- attachResult._status = RoomLifecycle .Failed
359
- attachResult._error = ErrorInfo (
388
+ attachResult.statusField = RoomLifecycle .Failed
389
+ attachResult.errorField = ErrorInfo (
360
390
" unexpected channel state in doAttach ${feature.channel.state}${feature.channel.errorMessage} " ,
361
- 500 ,
362
- feature.contributor.attachmentErrorCode .errorCode,
391
+ HttpStatusCodes . InternalServerError ,
392
+ ErrorCodes . RoomLifecycleError .errorCode,
363
393
)
364
394
}
365
395
}
@@ -395,7 +425,7 @@ class RoomLifecycleManager
395
425
while (channelWindDown.isFailure) { // CHA-RL1h6 - repeat until all channels are detached
396
426
// Something went wrong during the wind down. After a short delay, to give others a turn, we should run down
397
427
// again until we reach a suitable conclusion.
398
- delay(250 )
428
+ delay(_retryDurationInMs )
399
429
channelWindDown = kotlin.runCatching { doChannelWindDown() }
400
430
}
401
431
}
@@ -408,6 +438,7 @@ class RoomLifecycleManager
408
438
* @returns Success/Failure when all channels are detached or at least one of them fails.
409
439
*
410
440
*/
441
+ @SuppressWarnings(" CognitiveComplexMethod" , " ComplexCondition" )
411
442
private suspend fun doChannelWindDown (except : ResolvedContributor ? = null) = coroutineScope {
412
443
_contributors .map { contributor: ResolvedContributor ->
413
444
async {
@@ -417,12 +448,12 @@ class RoomLifecycleManager
417
448
return @async
418
449
}
419
450
// If the room's already in the failed state, or it's releasing, we should not detach a failed channel
420
- if (
421
- (
451
+ if ((
422
452
_status .current == = RoomLifecycle .Failed ||
423
453
_status .current == = RoomLifecycle .Releasing ||
424
454
_status .current == = RoomLifecycle .Released
425
- ) && contributor.channel.state == = ChannelState .failed
455
+ ) &&
456
+ contributor.channel.state == = ChannelState .failed
426
457
) {
427
458
return @async
428
459
}
@@ -439,7 +470,7 @@ class RoomLifecycleManager
439
470
) {
440
471
val contributorError = ErrorInfo (
441
472
" failed to detach feature" ,
442
- 500 ,
473
+ HttpStatusCodes . InternalServerError ,
443
474
contributor.contributor.detachmentErrorCode.errorCode,
444
475
)
445
476
_status .setStatus(RoomLifecycle .Failed , contributorError)
0 commit comments