Skip to content
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

EventLoopFuture/Promise: only Sendable if Value is Sendable #2496

Merged
merged 3 commits into from
Aug 8, 2023

Conversation

weissi
Copy link
Member

@weissi weissi commented Aug 7, 2023

Motivation:

EventLoopFuture/Promise are (incorrectly) marked as unconditionally Sendable. But this allows to send non-Sendable values which isn't right.

Modifications:

Mark EventLoopFuture/Promise as conditionally Sendable if Value is Sendable.

This is technically API breaking but given the nature of the Sendable protocol should only result in warnings in the very most cases. The NIO release tools will assess compatibility.

Result:

  • More correctness
  • More warnings for users (sorry NIO users)

@weissi weissi requested a review from dnadoba August 7, 2023 13:38
@Lukasa Lukasa added the 🔼 needs-minor-version-bump For PRs that when merged cause a bump of the minor version, ie. 1.x.0 -> 1.(x+1).0 label Aug 7, 2023
Copy link
Contributor

@Lukasa Lukasa left a comment

Choose a reason for hiding this comment

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

I'm remarkably nervous about merging this, but it must not merge until immediately after a release. We need some time for the release validation to bake. Marking "request changes" to block an accidental merge.

@weissi
Copy link
Member Author

weissi commented Aug 7, 2023

I'm remarkably nervous about merging this, but it must not merge until immediately after a release. We need some time for the release validation to bake. Marking "request changes" to block an accidental merge.

Good idea. Yes, I'd suggest to make a NIO minor release JUST for this PR :). If something goes side-ways we can "repair" it by undoing it in a follow-up patch version (think in SemVer this is allowed if something was done accidentally).

@ktoso
Copy link
Member

ktoso commented Aug 7, 2023

Overall it’s a good idea, but I support the careful rollout 👍

@dnadoba
Copy link
Member

dnadoba commented Aug 7, 2023

Also very nervous about this, especially about Swift 5.6/5.7. But it is the right thing to do.

@weissi
Copy link
Member Author

weissi commented Aug 7, 2023

Also very nervous about this, especially about Swift 5.6/5.7. But it is the right thing to do.

I mean we could (not saying we necessarily should) #if compiler(>5.9) this whole thing and then nothing changes until 5.9 is released :P.

@Lukasa
Copy link
Contributor

Lukasa commented Aug 7, 2023

Sadly that's still a source break 😉

@weissi
Copy link
Member Author

weissi commented Aug 7, 2023

Sadly that's still a source break 😉

No disagreement here

Copy link
Contributor

@Lukasa Lukasa left a comment

Choose a reason for hiding this comment

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

OK, I think we can land this and see what happens.

@Lukasa
Copy link
Contributor

Lukasa commented Aug 8, 2023

FYI @simonjbeaumont who is monitoring the release tools at this time.

@Lukasa Lukasa enabled auto-merge (squash) August 8, 2023 10:34
@Lukasa Lukasa merged commit ac695eb into apple:main Aug 8, 2023
6 of 8 checks passed
FranzBusch added a commit to FranzBusch/swift-nio that referenced this pull request Aug 11, 2023
# Motivation
With apple#2496 we fixed a Sendability checking hole by removing the unconditional conformance of `EventLoopFuture/Promise` to `Sendable`. This was the correct thing to do; however, it has the fallout that a couple of methods are now rightly complaining that their values are send across isolation domains.

# Modification
This PR requires values on some `ELF/P` methods to be `Sendable` when we might potentially transfer the values across isolation domains/ELs. We have to be overly aggressive here because we do not know that some `ELF` method are staying on the same EL. For example `flatMap` gets a new `ELF` from the closure provided to it. If the `ELF` is on the same EL we do not need to hop; however, we can not guarantee this right now from a type level so we have to stay on the safe side and actually require the `NewValue` to be `Sendable`.

# Result
This PR makes us more correct from a Sendability perspective but produces warnings for some safe patterns that are currently in use.
FranzBusch added a commit to FranzBusch/swift-nio that referenced this pull request Aug 11, 2023
# Motivation
With apple#2496 we fixed a Sendability checking hole by removing the unconditional conformance of `EventLoopFuture/Promise` to `Sendable`. This was the correct thing to do; however, it has the fallout that a couple of methods are now rightly complaining that their values are send across isolation domains.

# Modification
This PR requires values on some `ELF/P` methods to be `Sendable` when we might potentially transfer the values across isolation domains/ELs. We have to be overly aggressive here because we do not know that some `ELF` method are staying on the same EL. For example `flatMap` gets a new `ELF` from the closure provided to it. If the `ELF` is on the same EL we do not need to hop; however, we can not guarantee this right now from a type level so we have to stay on the safe side and actually require the `NewValue` to be `Sendable`.

# Result
This PR makes us more correct from a Sendability perspective but produces warnings for some safe patterns that are currently in use.
FranzBusch added a commit to FranzBusch/swift-nio that referenced this pull request Feb 16, 2024
… checking

# Motivation

We need to tackle the remaining strict concurrency checking related `Sendable` warnings in NIO. The first place to start is making sure that `EventLoopFuture` and `EventLoopPromise` are properly annotated.

# Modification

In a previous apple#2496, @weissi changed the `@unchecked Sendable` conformances of `EventLoopFuture/Promise` to be conditional on the sendability of the generic `Value` type. After having looked at all the APIs on the future and promise types as well as reading the latest Concurrency evolution proposals, specifically the [Region based Isolation](https://github.com/apple/swift-evolution/blob/main/proposals/0414-region-based-isolation.md), I came to the conclusion that the previous `@unchecked Sendable` annotations were correct. The reasoning for this is:

1. An `EventLoopPromise` and `EventLoopFuture` pair are tied to a specific `EventLoop`
2. An `EventLoop` represents an isolation region and values tied to its isolation are not allowed to be shared outside of it unless they are disconnected from the region
3. The `value` used to succeed a promise often come from outside the isolation domain of the `EventLoop` hence they must be transferred into the promise.
4. The isolation region of the event loop is enforced through `@Sendable` annotations on all closures that receive the value in some kind of transformation e.g. `map()`
5. Any method on `EventLoopFuture` that combines itself with another future must require `Sendable` of the other futures `Value` since we cannot statically enforce that futures are bound to the same event loop i.e. to the same isolation domain

Due to the above rules, this PR adds back the `@unchecked Sendable` conformances to both types. Furthermore, this PR revisits every single method on `EventLoopPromise/Future` and adds missing `Sendable` and `@Sendable` annotation where necessary to uphold the above rules. A few important things to call out:

- Since `transferring` is currently not available this PR requires a `Sendable` conformance for some methods on `EventLoopPromise/Future` that should rather take a `transffering` argument
- To enable the common case where a value from the same event loop is used to succeed a promise I added two additional methods that take a `eventLoopBoundResult` and enforce dynamic isolation checking. We might have to do this for more methods once we adopt those changes in other targets/packages.

# Result

After this PR has landed our lowest level building block should be inline with what the rest of the language enforces in Concurrency. The `EventLoopFuture.swift` produces no more warnings under strict concurrency checking on the latest 5.10 snapshots.
FranzBusch added a commit to FranzBusch/swift-nio that referenced this pull request Feb 16, 2024
… checking

# Motivation

We need to tackle the remaining strict concurrency checking related `Sendable` warnings in NIO. The first place to start is making sure that `EventLoopFuture` and `EventLoopPromise` are properly annotated.

# Modification

In a previous apple#2496, @weissi changed the `@unchecked Sendable` conformances of `EventLoopFuture/Promise` to be conditional on the sendability of the generic `Value` type. After having looked at all the APIs on the future and promise types as well as reading the latest Concurrency evolution proposals, specifically the [Region based Isolation](https://github.com/apple/swift-evolution/blob/main/proposals/0414-region-based-isolation.md), I came to the conclusion that the previous `@unchecked Sendable` annotations were correct. The reasoning for this is:

1. An `EventLoopPromise` and `EventLoopFuture` pair are tied to a specific `EventLoop`
2. An `EventLoop` represents an isolation region and values tied to its isolation are not allowed to be shared outside of it unless they are disconnected from the region
3. The `value` used to succeed a promise often come from outside the isolation domain of the `EventLoop` hence they must be transferred into the promise.
4. The isolation region of the event loop is enforced through `@Sendable` annotations on all closures that receive the value in some kind of transformation e.g. `map()`
5. Any method on `EventLoopFuture` that combines itself with another future must require `Sendable` of the other futures `Value` since we cannot statically enforce that futures are bound to the same event loop i.e. to the same isolation domain

Due to the above rules, this PR adds back the `@unchecked Sendable` conformances to both types. Furthermore, this PR revisits every single method on `EventLoopPromise/Future` and adds missing `Sendable` and `@Sendable` annotation where necessary to uphold the above rules. A few important things to call out:

- Since `transferring` is currently not available this PR requires a `Sendable` conformance for some methods on `EventLoopPromise/Future` that should rather take a `transffering` argument
- To enable the common case where a value from the same event loop is used to succeed a promise I added two additional methods that take a `eventLoopBoundResult` and enforce dynamic isolation checking. We might have to do this for more methods once we adopt those changes in other targets/packages.

# Result

After this PR has landed our lowest level building block should be inline with what the rest of the language enforces in Concurrency. The `EventLoopFuture.swift` produces no more warnings under strict concurrency checking on the latest 5.10 snapshots.
FranzBusch added a commit to FranzBusch/swift-nio that referenced this pull request Feb 19, 2024
… checking

# Motivation

We need to tackle the remaining strict concurrency checking related `Sendable` warnings in NIO. The first place to start is making sure that `EventLoopFuture` and `EventLoopPromise` are properly annotated.

# Modification

In a previous apple#2496, @weissi changed the `@unchecked Sendable` conformances of `EventLoopFuture/Promise` to be conditional on the sendability of the generic `Value` type. After having looked at all the APIs on the future and promise types as well as reading the latest Concurrency evolution proposals, specifically the [Region based Isolation](https://github.com/apple/swift-evolution/blob/main/proposals/0414-region-based-isolation.md), I came to the conclusion that the previous `@unchecked Sendable` annotations were correct. The reasoning for this is:

1. An `EventLoopPromise` and `EventLoopFuture` pair are tied to a specific `EventLoop`
2. An `EventLoop` represents an isolation region and values tied to its isolation are not allowed to be shared outside of it unless they are disconnected from the region
3. The `value` used to succeed a promise often come from outside the isolation domain of the `EventLoop` hence they must be transferred into the promise.
4. The isolation region of the event loop is enforced through `@Sendable` annotations on all closures that receive the value in some kind of transformation e.g. `map()`
5. Any method on `EventLoopFuture` that combines itself with another future must require `Sendable` of the other futures `Value` since we cannot statically enforce that futures are bound to the same event loop i.e. to the same isolation domain

Due to the above rules, this PR adds back the `@unchecked Sendable` conformances to both types. Furthermore, this PR revisits every single method on `EventLoopPromise/Future` and adds missing `Sendable` and `@Sendable` annotation where necessary to uphold the above rules. A few important things to call out:

- Since `transferring` is currently not available this PR requires a `Sendable` conformance for some methods on `EventLoopPromise/Future` that should rather take a `transffering` argument
- To enable the common case where a value from the same event loop is used to succeed a promise I added two additional methods that take a `eventLoopBoundResult` and enforce dynamic isolation checking. We might have to do this for more methods once we adopt those changes in other targets/packages.

# Result

After this PR has landed our lowest level building block should be inline with what the rest of the language enforces in Concurrency. The `EventLoopFuture.swift` produces no more warnings under strict concurrency checking on the latest 5.10 snapshots.
FranzBusch added a commit to FranzBusch/swift-nio that referenced this pull request Feb 19, 2024
… checking

# Motivation

We need to tackle the remaining strict concurrency checking related `Sendable` warnings in NIO. The first place to start is making sure that `EventLoopFuture` and `EventLoopPromise` are properly annotated.

# Modification

In a previous apple#2496, @weissi changed the `@unchecked Sendable` conformances of `EventLoopFuture/Promise` to be conditional on the sendability of the generic `Value` type. After having looked at all the APIs on the future and promise types as well as reading the latest Concurrency evolution proposals, specifically the [Region based Isolation](https://github.com/apple/swift-evolution/blob/main/proposals/0414-region-based-isolation.md), I came to the conclusion that the previous `@unchecked Sendable` annotations were correct. The reasoning for this is:

1. An `EventLoopPromise` and `EventLoopFuture` pair are tied to a specific `EventLoop`
2. An `EventLoop` represents an isolation region and values tied to its isolation are not allowed to be shared outside of it unless they are disconnected from the region
3. The `value` used to succeed a promise often come from outside the isolation domain of the `EventLoop` hence they must be transferred into the promise.
4. The isolation region of the event loop is enforced through `@Sendable` annotations on all closures that receive the value in some kind of transformation e.g. `map()`
5. Any method on `EventLoopFuture` that combines itself with another future must require `Sendable` of the other futures `Value` since we cannot statically enforce that futures are bound to the same event loop i.e. to the same isolation domain

Due to the above rules, this PR adds back the `@unchecked Sendable` conformances to both types. Furthermore, this PR revisits every single method on `EventLoopPromise/Future` and adds missing `Sendable` and `@Sendable` annotation where necessary to uphold the above rules. A few important things to call out:

- Since `transferring` is currently not available this PR requires a `Sendable` conformance for some methods on `EventLoopPromise/Future` that should rather take a `transffering` argument
- To enable the common case where a value from the same event loop is used to succeed a promise I added two additional methods that take a `eventLoopBoundResult` and enforce dynamic isolation checking. We might have to do this for more methods once we adopt those changes in other targets/packages.

# Result

After this PR has landed our lowest level building block should be inline with what the rest of the language enforces in Concurrency. The `EventLoopFuture.swift` produces no more warnings under strict concurrency checking on the latest 5.10 snapshots.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🔼 needs-minor-version-bump For PRs that when merged cause a bump of the minor version, ie. 1.x.0 -> 1.(x+1).0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants