Skip to content

[Distributed] Fix too restrictive distributed witness isolation checking #59358

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

Merged
merged 4 commits into from
Jun 14, 2022

Conversation

ktoso
Copy link
Contributor

@ktoso ktoso commented Jun 10, 2022

Resolves rdar://94779780
Blocked by: #59356


Distributed witness checking is implemented in order to avoid "peeling off" the distributed-ness from a distributed actor.
We cannot allow upcasting a DA to some arbitrary protocol P, where that protocol P requirement isn't async + throws, because cross-actor calls must always have this effect.

We missed to handle an edge case in the model, which is requirements declared on a protocol DAP: DistributedActor which is where we CANNOT "peel off" the distributedness off the distributed actor, by upcasting it to that DAP.

protocol WatchingDA: DistributedActor {
  func terminated(da: String) async
}

distributed actor DA_WatchingDA: WatchingDA {
  func terminated(da: String) { } // should be fine
}

// The isolation checking itself is UNCHANGED and relies on the usual rules:
func test<WDA: WatchingDA>(da: WDA) async throws {
  try await da.terminated(da: "") // error: because of the normal distributed isolation checking rules; nothing new here
  await da.whenLocal { await $0.terminated(da: "OK") } // this is allowed
  // self.terminated(da:) is of course also allowed

  await da.whenLocal { $0.terminated(da: "OK") } // crashes because of  #59356
}

Errors forcing us to adopt nonisolated or distributed for this method; while it is strictly speaking not necessary.

We CAN witness this method, but it will be impossible to call when erased to WatchingDA because distributed actor isolation will prevent it from being called (!),
and this is exactly what we want. To only be able to call "inside" or "when known to be local", which is how frameworks can implement their lifecycle management hooks.

Example:

In practice this missing checking rule caused us to have to write such terrible workarounds:

    public nonisolated func terminated(actor id: ActorID) async { // HORRIBLE; we know we are the local actor here always when this is called (because OUTER caller does this whenLocal dance)
       await self.whenLocal { __secretlyKnownToBeLocal in
           await __secretlyKnownToBeLocal.probe.tell("Received terminated: \(id)")
        }
    }
     public distributed func terminated(actor id: ActorID) async { // REALLY NOT distributed
         self.probe.tell("Received terminated: \(id)")
     }

@ktoso ktoso requested a review from DougGregor June 10, 2022 10:04
@ktoso ktoso added the distributed Feature → concurrency: distributed actor label Jun 10, 2022
@ktoso
Copy link
Contributor Author

ktoso commented Jun 10, 2022

I did tests with working around the limitation of actor isolation with generics -- once #59356 is done we can undo the hack here 👍

@ktoso
Copy link
Contributor Author

ktoso commented Jun 10, 2022

@swift-ci please smoke test

Copy link
Member

@DougGregor DougGregor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks okay, but I think it can be simplified somewhat.


// If we're coming from a non-distributed requirement,
// then the requirement must be 'throws' to accommodate failure.
if (!isDistributedDecl(requirement) && !isThrowsDecl(requirement))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't have distributed requirements outside of DistributedActor-based protocols, right? So we don't need this isDistributedDecl check?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or did you mean isDistributedDecl(witness)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I revisited this all, added more tests and simplified: 8e6d721

@ktoso
Copy link
Contributor Author

ktoso commented Jun 14, 2022

Thank you! I'll dig into the comments 🙏

@ktoso
Copy link
Contributor Author

ktoso commented Jun 14, 2022

@swift-ci please smoke test and merge

@swift-ci swift-ci merged commit 35355af into main Jun 14, 2022
@swift-ci swift-ci deleted the wip-missing-witness-allowance branch June 14, 2022 08:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
distributed Feature → concurrency: distributed actor
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants