-
Notifications
You must be signed in to change notification settings - Fork 78
Add API to 'await joining cluster' #972
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
0df67f4
8b1d1c0
11f0381
338d2d0
f378526
ea630df
88a1ad6
dbefc27
1f841d6
bce275d
5178fa4
d1dbeb4
21f717c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -155,4 +155,116 @@ public struct ClusterControl { | |
| public func down(member: Cluster.Member) { | ||
| self.ref.tell(.command(.downCommandMember(member))) | ||
| } | ||
|
|
||
| /// Wait, within the given duration, until this actor system has joined the node's cluster. | ||
| /// | ||
| /// - Parameters | ||
| /// - node: The node to be joined by this system. | ||
| /// - within: Duration to wait for. | ||
| /// | ||
| /// - Returns `Cluster.Member` for the joined node. | ||
| public func joined(node: UniqueNode, within: Duration) async throws -> Cluster.Member { | ||
| try await self.waitFor(node, .up, within: within) | ||
| } | ||
yim-lee marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /// Wait, within the given duration, for this actor system to be a member of all the nodes' respective cluster and have the specified status. | ||
| /// | ||
| /// - Parameters | ||
| /// - nodes: The nodes to be joined by this system. | ||
| /// - status: The expected member status. | ||
| /// - within: Duration to wait for. | ||
| public func waitFor(_ nodes: Set<UniqueNode>, _ status: Cluster.MemberStatus, within: Duration) async throws { | ||
| try await withThrowingTaskGroup(of: Void.self) { group in | ||
| for node in nodes { | ||
| group.addTask { | ||
| _ = try await self.waitFor(node, status, within: within) | ||
| } | ||
| } | ||
| // loop explicitly to propagagte any error that might have been thrown | ||
| for try await _ in group {} | ||
| } | ||
| } | ||
|
|
||
| /// Wait, within the given duration, for this actor system to be a member of all the nodes' respective cluster and have **at least** the specified status. | ||
| /// | ||
| /// - Parameters | ||
| /// - nodes: The nodes to be joined by this system. | ||
| /// - status: The minimum expected member status. | ||
| /// - within: Duration to wait for. | ||
| public func waitFor(_ nodes: Set<UniqueNode>, atLeast atLeastStatus: Cluster.MemberStatus, within: Duration) async throws { | ||
| try await withThrowingTaskGroup(of: Void.self) { group in | ||
| for node in nodes { | ||
| group.addTask { | ||
| _ = try await self.waitFor(node, atLeast: atLeastStatus, within: within) | ||
| } | ||
| } | ||
| // loop explicitly to propagagte any error that might have been thrown | ||
| for try await _ in group {} | ||
| } | ||
| } | ||
|
|
||
| /// Wait, within the given duration, for this actor system to be a member of the node's cluster and have the specified status. | ||
| /// | ||
| /// - Parameters | ||
| /// - node: The node to be joined by this system. | ||
| /// - status: The expected member status. | ||
| /// - within: Duration to wait for. | ||
| /// | ||
| /// - Returns `Cluster.Member` for the joined node. | ||
| public func waitFor(_ node: UniqueNode, _ status: Cluster.MemberStatus, within: Duration) async throws -> Cluster.Member { | ||
| try await self.waitForMembershipEventually(within: within) { membership in | ||
| guard let foundMember = membership.uniqueMember(node) else { | ||
| throw Cluster.MembershipError.notFound(node, in: membership) | ||
| } | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh sorry to bother you but I thought of another case... If a node was removed BEFORE, we'll have a tombstone for it inside the You can "make up" a member for a removed node by creating a This could be in a separate PR 👍
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. amended 5178fa4
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking good! |
||
| if status != foundMember.status { | ||
| throw Cluster.MembershipError.statusRequirementNotMet(expected: status, found: foundMember) | ||
| } | ||
| return foundMember | ||
| } | ||
| } | ||
|
|
||
| /// Wait, within the given duration, for this actor system to be a member of the node's cluster and have **at least** the specified status. | ||
| /// | ||
| /// - Parameters | ||
| /// - node: The node to be joined by this system. | ||
| /// - atLeastStatus: The minimum expected member status. | ||
| /// - within: Duration to wait for. | ||
| /// | ||
| /// - Returns `Cluster.Member` for the joined node or `nil` if member is expected to be down or removed. | ||
| public func waitFor(_ node: UniqueNode, atLeast atLeastStatus: Cluster.MemberStatus, within: Duration) async throws -> Cluster.Member? { | ||
| try await self.waitForMembershipEventually(within: within) { membership in | ||
| guard let foundMember = membership.uniqueMember(node) else { | ||
| if atLeastStatus == .down || atLeastStatus == .removed { | ||
| // so we're seeing an already removed member, this can indeed happen and is okey | ||
| return nil | ||
| } else { | ||
| throw Cluster.MembershipError.notFound(node, in: membership) | ||
| } | ||
| } | ||
|
|
||
| if atLeastStatus <= foundMember.status { | ||
| throw Cluster.MembershipError.atLeastStatusRequirementNotMet(expectedAtLeast: atLeastStatus, found: foundMember) | ||
| } | ||
| return foundMember | ||
| } | ||
| } | ||
|
|
||
| private func waitForMembershipEventually<T>(within: Duration, interval: Duration = .milliseconds(100), _ block: (Cluster.Membership) async throws -> T) async throws -> T { | ||
| let deadline = ContinuousClock.Instant.fromNow(within) | ||
|
|
||
| var lastError: Error? | ||
| while deadline.hasTimeLeft() { | ||
| let membership = await self.membershipSnapshot | ||
| do { | ||
| let result = try await block(membership) | ||
| return result | ||
| } catch { | ||
| lastError = error | ||
| try await Task.sleep(nanoseconds: UInt64(interval.nanoseconds)) | ||
| } | ||
| } | ||
|
|
||
| throw Cluster.MembershipError.awaitStatusTimedOut(within, lastError) | ||
| } | ||
| } | ||
ktoso marked this conversation as resolved.
Show resolved
Hide resolved
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good but Perhaps must be wrapped in
"MembershipError(\(self), details: <the nice strings>")? Otherwise might be weird not seeing what type the error was?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
amended bce275d