-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[Distributed] Enforce strict order of synthesized id and actorSystem properties; avoid wrong offset crashes #58745
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 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 |
---|---|---|
|
@@ -426,32 +426,6 @@ static FuncDecl *deriveDistributedActorSystem_invokeHandlerOnReturn( | |
/******************************* PROPERTIES ***********************************/ | ||
/******************************************************************************/ | ||
|
||
// TODO(distributed): make use of this after all, but FORCE it? | ||
static ValueDecl *deriveDistributedActor_id(DerivedConformance &derived) { | ||
assert(derived.Nominal->isDistributedActor()); | ||
auto &C = derived.Context; | ||
|
||
// ``` | ||
// nonisolated | ||
// let id: Self.ID // Self.ActorSystem.ActorID | ||
// ``` | ||
auto propertyType = getDistributedActorIDType(derived.Nominal); | ||
|
||
VarDecl *propDecl; | ||
PatternBindingDecl *pbDecl; | ||
std::tie(propDecl, pbDecl) = derived.declareDerivedProperty( | ||
DerivedConformance::SynthesizedIntroducer::Let, C.Id_id, propertyType, | ||
propertyType, | ||
/*isStatic=*/false, /*isFinal=*/true); | ||
|
||
// mark as nonisolated, allowing access to it from everywhere | ||
propDecl->getAttrs().add( | ||
new (C) NonisolatedAttr(/*IsImplicit=*/true)); | ||
|
||
derived.addMembersToConformanceContext({ propDecl, pbDecl }); | ||
return propDecl; | ||
} | ||
|
||
static ValueDecl *deriveDistributedActor_actorSystem( | ||
DerivedConformance &derived) { | ||
auto &C = derived.Context; | ||
|
@@ -460,8 +434,7 @@ static ValueDecl *deriveDistributedActor_actorSystem( | |
assert(classDecl && derived.Nominal->isDistributedActor()); | ||
|
||
// ``` | ||
// nonisolated | ||
// let actorSystem: ActorSystem | ||
// nonisolated let actorSystem: ActorSystem | ||
// ``` | ||
// (no need for @actorIndependent because it is an immutable let) | ||
auto propertyType = getDistributedActorSystemType(classDecl); | ||
|
@@ -477,7 +450,14 @@ static ValueDecl *deriveDistributedActor_actorSystem( | |
propDecl->getAttrs().add( | ||
new (C) NonisolatedAttr(/*IsImplicit=*/true)); | ||
|
||
derived.addMembersToConformanceContext({ propDecl, pbDecl }); | ||
// IMPORTANT: `id` MUST be the first field of a distributed actor, and | ||
// `actorSystem` MUST be the second field, because for a remote instance | ||
// we don't allocate memory after those two fields, so their order is very | ||
// important. The `hint` below makes sure the system is inserted right after. | ||
auto id = derived.Nominal->getDistributedActorIDProperty(); | ||
derived.addMemberToConformanceContext(pbDecl, /*hint=*/id); | ||
derived.addMemberToConformanceContext(propDecl, /*hint=*/id); | ||
|
||
return propDecl; | ||
} | ||
|
||
|
@@ -571,11 +551,14 @@ deriveDistributedActorType_SerializationRequirement( | |
|
||
ValueDecl *DerivedConformance::deriveDistributedActor(ValueDecl *requirement) { | ||
if (auto var = dyn_cast<VarDecl>(requirement)) { | ||
if (var->getName() == Context.Id_id) | ||
return deriveDistributedActor_id(*this); | ||
|
||
if (var->getName() == Context.Id_actorSystem) | ||
return deriveDistributedActor_actorSystem(*this); | ||
|
||
if (var->getName() == Context.Id_id) | ||
llvm_unreachable("DistributedActor.id MUST be synthesized earlier, " | ||
"because it is forced by the Identifiable conformance. " | ||
"If we attempted to do synthesis here, the earlier phase " | ||
"failed and something is wrong: please report a bug."); | ||
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. We had to move away from its synthesis here a while back so this was dead code; Actually I should just delete the hangling here I guess. |
||
} | ||
|
||
if (auto func = dyn_cast<FuncDecl>(requirement)) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,6 @@ | |
//===----------------------------------------------------------------------===// | ||
|
||
import Swift | ||
import _Concurrency | ||
|
||
#if canImport(Darwin) | ||
import Darwin | ||
|
@@ -27,33 +26,25 @@ import WinSDK | |
/// for learning about `distributed actor` isolation, as well as early | ||
/// prototyping stages of development where a real system is not necessary yet. | ||
@available(SwiftStdlib 5.7, *) | ||
public struct LocalTestingDistributedActorSystem: DistributedActorSystem, @unchecked Sendable { | ||
public final class LocalTestingDistributedActorSystem: DistributedActorSystem, @unchecked Sendable { | ||
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. This UNDOES the workaround (which actually did not work after all). This is NOT a change in the API of the system |
||
public typealias ActorID = LocalTestingActorAddress | ||
public typealias ResultHandler = LocalTestingInvocationResultHandler | ||
public typealias InvocationEncoder = LocalTestingInvocationEncoder | ||
public typealias InvocationDecoder = LocalTestingInvocationDecoder | ||
public typealias SerializationRequirement = any Codable | ||
|
||
|
||
@usableFromInline | ||
final class _Storage { | ||
public typealias SerializationRequirement = Codable | ||
|
||
var activeActors: [ActorID: any DistributedActor] = [:] | ||
let activeActorsLock = _Lock() | ||
private var activeActors: [ActorID: any DistributedActor] = [:] | ||
private let activeActorsLock = _Lock() | ||
|
||
var assignedIDs: Set<ActorID> = [] | ||
let assignedIDsLock = _Lock() | ||
} | ||
let storage: _Storage | ||
var idProvider: ActorIDProvider = ActorIDProvider() | ||
private var idProvider: ActorIDProvider = ActorIDProvider() | ||
private var assignedIDs: Set<ActorID> = [] | ||
private let assignedIDsLock = _Lock() | ||
|
||
public init() { | ||
storage = .init() | ||
} | ||
public init() {} | ||
|
||
public func resolve<Act>(id: ActorID, as actorType: Act.Type) | ||
throws -> Act? where Act: DistributedActor, Act.ID == ActorID { | ||
guard let anyActor = storage.activeActorsLock.withLock({ self.storage.activeActors[id] }) else { | ||
guard let anyActor = self.activeActorsLock.withLock({ self.activeActors[id] }) else { | ||
throw LocalTestingDistributedActorSystemError(message: "Unable to locate id '\(id)' locally") | ||
} | ||
guard let actor = anyActor as? Act else { | ||
|
@@ -65,23 +56,25 @@ public struct LocalTestingDistributedActorSystem: DistributedActorSystem, @unche | |
public func assignID<Act>(_ actorType: Act.Type) -> ActorID | ||
where Act: DistributedActor, Act.ID == ActorID { | ||
let id = self.idProvider.next() | ||
storage.assignedIDsLock.withLock { | ||
self.storage.assignedIDs.insert(id) | ||
self.assignedIDsLock.withLock { | ||
self.assignedIDs.insert(id) | ||
} | ||
return id | ||
} | ||
|
||
public func actorReady<Act>(_ actor: Act) | ||
where Act: DistributedActor, Act.ID == ActorID { | ||
guard storage.assignedIDsLock.withLock({ self.storage.assignedIDs.contains(actor.id) }) else { | ||
guard self.assignedIDsLock.withLock({ self.assignedIDs.contains(actor.id) }) else { | ||
fatalError("Attempted to mark an unknown actor '\(actor.id)' ready") | ||
} | ||
self.storage.activeActors[actor.id] = actor | ||
self.activeActorsLock.withLock { | ||
self.activeActors[actor.id] = actor | ||
} | ||
} | ||
|
||
public func resignID(_ id: ActorID) { | ||
storage.activeActorsLock.withLock { | ||
self.storage.activeActors.removeValue(forKey: id) | ||
self.activeActorsLock.withLock { | ||
self.activeActors.removeValue(forKey: id) | ||
} | ||
} | ||
|
||
|
@@ -99,7 +92,7 @@ public struct LocalTestingDistributedActorSystem: DistributedActorSystem, @unche | |
where Act: DistributedActor, | ||
Act.ID == ActorID, | ||
Err: Error, | ||
Res: Codable { | ||
Res: SerializationRequirement { | ||
fatalError("Attempted to make remote call to \(target) on actor \(actor) using a local-only actor system") | ||
} | ||
|
||
|
@@ -115,40 +108,51 @@ public struct LocalTestingDistributedActorSystem: DistributedActorSystem, @unche | |
fatalError("Attempted to make remote call to \(target) on actor \(actor) using a local-only actor system") | ||
} | ||
|
||
@usableFromInline | ||
final class ActorIDProvider { | ||
private struct ActorIDProvider { | ||
private var counter: Int = 0 | ||
private let counterLock = _Lock() | ||
|
||
@usableFromInline | ||
init() {} | ||
|
||
func next() -> LocalTestingActorAddress { | ||
mutating func next() -> LocalTestingActorAddress { | ||
let id: Int = self.counterLock.withLock { | ||
self.counter += 1 | ||
return self.counter | ||
} | ||
return LocalTestingActorAddress(id) | ||
return LocalTestingActorAddress(parse: "\(id)") | ||
} | ||
} | ||
} | ||
|
||
@available(SwiftStdlib 5.7, *) | ||
public struct LocalTestingActorAddress: Hashable, Sendable, Codable { | ||
public let uuid: String | ||
@available(*, deprecated, renamed: "LocalTestingActorID") | ||
public typealias LocalTestingActorAddress = LocalTestingActorID | ||
|
||
public init(_ uuid: Int) { | ||
self.uuid = "\(uuid)" | ||
@available(SwiftStdlib 5.7, *) | ||
public struct LocalTestingActorID: Hashable, Sendable, Codable { | ||
@available(*, deprecated, renamed: "id") | ||
public var address: String { | ||
self.id | ||
} | ||
public let id: String | ||
|
||
@available(*, deprecated, renamed: "init(id:)") | ||
public init(parse id: String) { | ||
self.id = id | ||
} | ||
|
||
public init(id: String) { | ||
self.id = id | ||
} | ||
|
||
public init(from decoder: Decoder) throws { | ||
let container = try decoder.singleValueContainer() | ||
self.uuid = try container.decode(String.self) | ||
self.id = try container.decode(String.self) | ||
} | ||
|
||
public func encode(to encoder: Encoder) throws { | ||
var container = encoder.singleValueContainer() | ||
try container.encode(self.uuid) | ||
try container.encode(self.id) | ||
} | ||
} | ||
|
||
|
@@ -228,7 +232,7 @@ public struct LocalTestingDistributedActorSystemError: DistributedActorSystemErr | |
// === lock ---------------------------------------------------------------- | ||
|
||
@available(SwiftStdlib 5.7, *) | ||
internal class _Lock { | ||
fileprivate class _Lock { | ||
#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) | ||
private let underlying: UnsafeMutablePointer<os_unfair_lock> | ||
#elseif os(Windows) | ||
|
@@ -277,6 +281,7 @@ internal class _Lock { | |
#endif | ||
} | ||
|
||
|
||
@discardableResult | ||
func withLock<T>(_ body: () -> T) -> T { | ||
#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,8 +8,6 @@ | |
// UNSUPPORTED: use_os_stdlib | ||
// UNSUPPORTED: back_deployment_runtime | ||
|
||
// REQUIRES: rdar92910719 | ||
|
||
import Distributed | ||
|
||
enum MyError: Error { | ||
|
@@ -92,15 +90,6 @@ distributed actor MaybeAfterAssign { | |
} | ||
} | ||
|
||
distributed actor LocalTestingSystemDA { | ||
typealias ActorSystem = LocalTestingDistributedActorSystem | ||
var x: Int | ||
init() { | ||
actorSystem = .init() | ||
x = 100 | ||
} | ||
} | ||
|
||
distributed actor LocalTestingDA_Int { | ||
typealias ActorSystem = LocalTestingDistributedActorSystem | ||
var int: Int | ||
|
@@ -111,7 +100,6 @@ distributed actor LocalTestingDA_Int { | |
} | ||
} | ||
|
||
|
||
// ==== Fake Transport --------------------------------------------------------- | ||
|
||
struct ActorAddress: Sendable, Hashable, Codable { | ||
|
@@ -286,7 +274,7 @@ func test() async { | |
|
||
let localDA = LocalTestingDA_Int() | ||
print("localDA = \(localDA.id)") | ||
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. This would have crashed recently -- this patch fixes it. |
||
// CHECK: localDA = LocalTestingActorAddress(uuid: "1") | ||
// CHECK: localDA = LocalTestingActorID(id: "1") | ||
|
||
// the following tests fail to initialize the actor's identity. | ||
print("-- start of no-assign tests --") | ||
|
@@ -297,6 +285,7 @@ func test() async { | |
// CHECK-NOT: assign | ||
// CHECK: -- end of no-assign tests -- | ||
|
||
|
||
// resigns that come out of the deinits: | ||
|
||
// CHECK-DAG: resign id:ActorAddress(address: "[[ID1]]") | ||
|
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.
This ensures the system is always AFTER the ID