-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This is based on [1] at aa7455d. It’s generated some questions, which I’ve asked on that PR. I started implementing this as part of #19, before realising that implementing the spec is not the aim of that task. So, putting this work on hold until we pick it up again in #28. So far, only the ATTACH operation is implemented. [1] ably/specification#200
- Loading branch information
1 parent
9014b0c
commit 0af5132
Showing
7 changed files
with
556 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import Ably | ||
|
||
// TODO: integrate with the rest of the SDK | ||
internal actor RoomLifecycleManager { | ||
internal private(set) var current: RoomLifecycle = .initialized | ||
internal private(set) var error: ARTErrorInfo? | ||
|
||
private let logger: InternalLogger | ||
private let contributors: [RealtimeChannelProtocol] | ||
|
||
internal init(contributors: [RealtimeChannelProtocol] = [], logger: InternalLogger) { | ||
self.contributors = contributors | ||
self.logger = logger | ||
} | ||
|
||
internal init(forTestingWhatHappensWhenCurrentlyIn current: RoomLifecycle, contributors: [RealtimeChannelProtocol] = [], logger: InternalLogger) { | ||
self.current = current | ||
self.contributors = contributors | ||
self.logger = logger | ||
} | ||
|
||
// TODO: clean up old subscriptions (https://github.com/ably-labs/ably-chat-swift/issues/36) | ||
private var subscriptions: [Subscription<RoomStatusChange>] = [] | ||
|
||
internal func onChange(bufferingPolicy: BufferingPolicy) -> Subscription<RoomStatusChange> { | ||
let subscription: Subscription<RoomStatusChange> = .init(bufferingPolicy: bufferingPolicy) | ||
subscriptions.append(subscription) | ||
return subscription | ||
} | ||
|
||
/// Updates ``current`` and ``error`` and emits a status change event. | ||
private func changeStatus(to new: RoomLifecycle, error: ARTErrorInfo? = nil) { | ||
logger.log(message: "Transitioning from \(current) to \(new), error \(String(describing: error))", level: .info) | ||
let previous = current | ||
current = new | ||
self.error = error | ||
let statusChange = RoomStatusChange(current: current, previous: previous, error: error) | ||
emitStatusChange(statusChange) | ||
} | ||
|
||
private func emitStatusChange(_ change: RoomStatusChange) { | ||
for subscription in subscriptions { | ||
subscription.emit(change) | ||
} | ||
} | ||
|
||
/// Implements CHA-RL1’s `ATTACH` operation. | ||
internal func performAttachOperation() async throws { | ||
switch current { | ||
case .attached: | ||
// CHA-RL1a | ||
return | ||
case .releasing: | ||
// CHA-RL1b | ||
throw ARTErrorInfo(chatError: .attachWhenReleasing) | ||
case .released: | ||
// CHA-RL1c | ||
throw ARTErrorInfo(chatError: .attachWhenReleased) | ||
case .initialized, .suspended, .attaching, .detached, .detaching, .failed: | ||
break | ||
} | ||
|
||
// CHA-RL1e | ||
changeStatus(to: .attaching) | ||
|
||
// CHA-RL1f | ||
for contributor in contributors { | ||
do { | ||
logger.log(message: "Attaching contributor", level: .info) | ||
try await contributor.attachAsync() | ||
} catch { | ||
let contributorState = contributor.state | ||
logger.log(message: "Failed to attach contributor \(contributor), which is now in state \(contributorState), error \(error)", level: .info) | ||
|
||
switch contributorState { | ||
case .suspended: | ||
// CHA-RL1h2 | ||
guard let contributorError = contributor.errorReason else { | ||
// TODO: something about this | ||
preconditionFailure("Contributor entered SUSPENDED but its errorReason is not set") | ||
} | ||
|
||
let error = ARTErrorInfo(chatError: .channelAttachResultedInSuspended(underlyingError: contributorError)) | ||
changeStatus(to: .suspended, error: error) | ||
|
||
// CHA-RL1h3 | ||
throw contributorError | ||
case .failed: | ||
// CHA-RL1h4 | ||
guard let contributorError = contributor.errorReason else { | ||
// TODO: something about this | ||
preconditionFailure("Contributor entered FAILED but its errorReason is not set") | ||
} | ||
|
||
let error = ARTErrorInfo(chatError: .channelAttachResultedInFailed(underlyingError: contributorError)) | ||
changeStatus(to: .failed, error: error) | ||
|
||
// CHA-RL1h5 | ||
await detachNonFailedContributors() | ||
|
||
// CHA-RL1h1 | ||
throw contributorError | ||
default: | ||
// TODO: something about this; quite possible due to thread timing stuff | ||
preconditionFailure("Attach failure left contributor in unexpected state \(contributorState)") | ||
} | ||
} | ||
} | ||
|
||
// CHA-RL1g | ||
changeStatus(to: .attached) | ||
} | ||
|
||
/// Implements CHA-RL1h5’s "detach all channels that are not in the FAILED state". | ||
private func detachNonFailedContributors() async { | ||
for contributor in contributors where contributor.state != .failed { | ||
// CHA-RL1h6: Retry until detach succeeds | ||
while true { | ||
do { | ||
logger.log(message: "Detaching non-failed contributor \(contributor)", level: .info) | ||
try await contributor.detachAsync() | ||
break | ||
} catch { | ||
logger.log(message: "Failed to detach non-failed contributor \(contributor), error \(error). Retrying.", level: .info) | ||
// Loop repeats | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.