-
Notifications
You must be signed in to change notification settings - Fork 55
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
swift-clusterd PoC which serves as seed node #1155
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
---|---|---|
@@ -0,0 +1,46 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the Swift Distributed Actors open source project | ||
// | ||
// Copyright (c) 2020-2024 Apple Inc. and the Swift Distributed Actors project authors | ||
// Licensed under Apache License v2.0 | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.md for the list of Swift Distributed Actors project authors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import DistributedCluster | ||
|
||
import ArgumentParser | ||
|
||
@main | ||
struct ClusterDBoot: AsyncParsableCommand { | ||
@Option(name: .shortAndLong, help: "The port to bind the cluster daemon on.") | ||
var port: Int = ClusterDaemon.defaultEndpoint.port | ||
|
||
@Option(help: "The host address to bid the cluster daemon on.") | ||
var host: String = ClusterDaemon.defaultEndpoint.host | ||
|
||
mutating func run() async throws { | ||
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. Should not be |
||
let daemon = await ClusterSystem.startClusterDaemon(configuredWith: self.configure) | ||
|
||
#if DEBUG | ||
daemon.system.log.warning("RUNNING ClusterD DEBUG binary, operation is likely to be negatively affected. Please build/run the ClusterD process using '-c release' configuration!") | ||
#endif | ||
|
||
try await daemon.system.park() | ||
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. await is not needed, for now at least |
||
} | ||
|
||
func configure(_ settings: inout ClusterSystemSettings) { | ||
// other nodes will be discovering us, not the opposite | ||
settings.discovery = .init(static: []) | ||
|
||
settings.endpoint = Cluster.Endpoint( | ||
systemName: "clusterd", | ||
host: host, | ||
port: port) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the Swift Distributed Actors open source project | ||
// | ||
// Copyright (c) 2018-2022 Apple Inc. and the Swift Distributed Actors project authors | ||
// Licensed under Apache License v2.0 | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.md for the list of Swift Distributed Actors project authors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import Atomics | ||
import Backtrace | ||
import CDistributedActorsMailbox | ||
import Dispatch | ||
@_exported import Distributed | ||
import DistributedActorsConcurrencyHelpers | ||
import Foundation // for UUID | ||
import Logging | ||
import NIO | ||
|
||
extension ClusterSystem { | ||
public static func startClusterDaemon(configuredWith configureSettings: (inout ClusterSystemSettings) -> Void = { _ in () }) async -> ClusterDaemon { | ||
let system = await ClusterSystem("clusterd") { settings in | ||
settings.endpoint = ClusterDaemon.defaultEndpoint | ||
configureSettings(&settings) | ||
} | ||
|
||
return ClusterDaemon(system: system) | ||
} | ||
} | ||
|
||
public struct ClusterDaemon { | ||
public let system: ClusterSystem | ||
public var settings: ClusterSystemSettings { | ||
system.settings | ||
} | ||
|
||
public init(system: ClusterSystem) { | ||
self.system = system | ||
} | ||
} | ||
|
||
extension ClusterDaemon { | ||
|
||
/// Suspends until the ``ClusterSystem`` is terminated by a call to ``shutdown()``. | ||
public var terminated: Void { | ||
get async throws { | ||
try await self.system.terminated | ||
} | ||
} | ||
|
||
/// Returns `true` if the system was already successfully terminated (i.e. awaiting ``terminated`` would resume immediately). | ||
public var isTerminated: Bool { | ||
self.system.isTerminated | ||
} | ||
|
||
/// Forcefully stops this actor system and all actors that live within it. | ||
/// This is an asynchronous operation and will be executed on a separate thread. | ||
/// | ||
/// You can use `shutdown().wait()` to synchronously await on the system's termination, | ||
/// or provide a callback to be executed after the system has completed it's shutdown. | ||
/// | ||
/// - Returns: A `Shutdown` value that can be waited upon until the system has completed the shutdown. | ||
@discardableResult | ||
public func shutdown() throws -> ClusterSystem.Shutdown { | ||
try self.system.shutdown() | ||
} | ||
} | ||
|
||
extension ClusterDaemon { | ||
/// The default endpoint | ||
public static let defaultEndpoint = Cluster.Endpoint(host: "127.0.0.1", port: 3137) | ||
} | ||
|
||
internal distributed actor ClusterDaemonServant { | ||
typealias ActorSystem = ClusterSystem | ||
|
||
@ActorID.Metadata(\.wellKnown) | ||
public var wellKnownName: String | ||
|
||
init(system: ClusterSystem) async { | ||
self.actorSystem = system | ||
self.wellKnownName = "$cluster-daemon-servant" | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -404,31 +404,65 @@ protocol ClusterSystemInstrumentationProvider { | |
/// all the nodes of an existing cluster. | ||
public struct ServiceDiscoverySettings { | ||
let implementation: ServiceDiscoveryImplementation | ||
private let _initialize: (ClusterSystem) -> Void | ||
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. A bit unclear naming, took some time to figure out, guess something more specific about cluster daemon, like |
||
private let _subscribe: (@escaping (Result<[Cluster.Endpoint], Error>) -> Void, @escaping (CompletionReason) -> Void) -> CancellationToken? | ||
|
||
public init<Discovery, S>(_ implementation: Discovery, service: S) | ||
where Discovery: ServiceDiscovery, Discovery.Instance == Cluster.Endpoint, | ||
S == Discovery.Service | ||
{ | ||
self.implementation = .dynamic(AnyServiceDiscovery(implementation)) | ||
self._initialize = { _ in } | ||
self._subscribe = { onNext, onComplete in | ||
implementation.subscribe(to: service, onNext: onNext, onComplete: onComplete) | ||
} | ||
} | ||
|
||
init(clusterdEndpoint: Cluster.Endpoint) { | ||
self.implementation = .clusterDaemon(clusterdEndpoint) | ||
|
||
self._initialize = { system in | ||
system.log.info("Joining [clusterd] at \(clusterdEndpoint)") | ||
system.cluster.join(endpoint: clusterdEndpoint) | ||
} | ||
self._subscribe = { onNext, onComplete in | ||
return nil | ||
} | ||
} | ||
|
||
/// Locate the default `ClusterD` process and use it for discovering cluster nodes. | ||
/// | ||
/// | ||
public static var clusterd: Self { | ||
get { | ||
Self.clusterd(endpoint: nil) | ||
} | ||
} | ||
|
||
/// Locate the default `ClusterD` process and use it for discovering cluster nodes. | ||
public static func clusterd(endpoint: Cluster.Endpoint?) -> Self { | ||
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. That's neat, my initial impression was it's for local clustering, but guess we can define any endpoint. |
||
return ServiceDiscoverySettings(clusterdEndpoint: endpoint ?? ClusterDaemon.defaultEndpoint) | ||
} | ||
|
||
public init<Discovery, S>(_ implementation: Discovery, service: S, mapInstanceToNode transformer: @escaping (Discovery.Instance) throws -> Cluster.Endpoint) | ||
where Discovery: ServiceDiscovery, | ||
S == Discovery.Service | ||
{ | ||
let mappedDiscovery: MapInstanceServiceDiscovery<Discovery, Cluster.Endpoint> = implementation.mapInstance(transformer) | ||
self.implementation = .dynamic(AnyServiceDiscovery(mappedDiscovery)) | ||
self._initialize = { _ in } | ||
self._subscribe = { onNext, onComplete in | ||
mappedDiscovery.subscribe(to: service, onNext: onNext, onComplete: onComplete) | ||
} | ||
} | ||
|
||
public static func `seed`(nodes: Set<Cluster.Endpoint>) -> Self { | ||
.init(static: nodes) | ||
} | ||
|
||
public init(static nodes: Set<Cluster.Endpoint>) { | ||
self.implementation = .static(nodes) | ||
self._initialize = { _ in } | ||
self._subscribe = { onNext, _ in | ||
// Call onNext once and never again since the list of nodes doesn't change | ||
onNext(.success(Array(nodes))) | ||
|
@@ -441,12 +475,18 @@ public struct ServiceDiscoverySettings { | |
|
||
/// Similar to `ServiceDiscovery.subscribe` however it allows the handling of the listings to be generic and handled by the cluster system. | ||
/// This function is only intended for internal use by the `DiscoveryShell`. | ||
func subscribe(onNext nextResultHandler: @escaping (Result<[Cluster.Endpoint], Error>) -> Void, onComplete completionHandler: @escaping (CompletionReason) -> Void) -> CancellationToken? { | ||
func subscribe(onNext nextResultHandler: @escaping (Result<[Cluster.Endpoint], Error>) -> Void, | ||
onComplete completionHandler: @escaping (CompletionReason) -> Void) -> CancellationToken? { | ||
self._subscribe(nextResultHandler, completionHandler) | ||
} | ||
|
||
func initialize(_ system: ClusterSystem) -> Void { | ||
self._initialize(system) | ||
} | ||
|
||
enum ServiceDiscoveryImplementation { | ||
case `static`(Set<Cluster.Endpoint>) | ||
case clusterDaemon(Cluster.Endpoint) | ||
case dynamic(AnyServiceDiscovery) | ||
} | ||
} | ||
|
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.
Let's rename to
swift-clusterd
now? Or at least lowercasedclusterd
would be better.