Skip to content
This repository was archived by the owner on Aug 5, 2022. It is now read-only.

Commit f21ea75

Browse files
authored
Merge pull request #71 from swift-server/ff-concurrency-adoption-guideline
last followups on concurrency guide
2 parents 60ff832 + 4bc4afd commit f21ea75

File tree

1 file changed

+43
-35
lines changed

1 file changed

+43
-35
lines changed

docs/concurrency-adoption-guidelines.md

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,16 @@ In 2021 we saw structured concurrency and actors arrive with Swift 5.5. Now is a
99

1010
## What you can do right now
1111

12-
### `#if` guarding code using Concurrency
13-
14-
In order to have code using concurrency along with code not using concurrency, you may have to `#if` guard certain pieces of code. The correct way to do so is the following:
15-
16-
```swift
17-
#if compiler(>=5.5) && canImport(_Concurrency)
18-
...
19-
#endif
20-
```
21-
22-
Please note that you do _not_ need to _import_ the `_Concurrency` at all, if it is present it is imported automatically.
23-
24-
```swift
25-
#if compiler(>=5.5) && canImport(_Concurrency)
26-
// DO NOT DO THIS.
27-
// Instead don't do any import and it'll import automatically when possible.
28-
import _Concurrency
29-
#endif
30-
```
31-
32-
33-
3412
### API Design
3513

3614
Firstly, existing libraries should strive to add `async` functions where possible to their user-facing “surface” APIs in addition to existing `*Future` based APIs wherever possible. These additive APIs can be gated on the Swift version and can be added without breaking existing users' code, for example like this:
3715

3816
```swift
3917
extension Worker {
4018
func work() -> EventLoopFuture<Value> { ... }
41-
42-
#if swift(>=5.5)
19+
20+
#if compiler(>=5.5) && canImport(_Concurrency)
21+
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
4322
func work() async throws -> Value { ... }
4423
#endif
4524
}
@@ -51,12 +30,13 @@ Such adoption can begin immediately, and should not cause any issues to existing
5130

5231
### SwiftNIO helper functions
5332

54-
To allow an easy transition to async code, SwiftNIO offers a number of helper methods on `EventLoopFuture` and `-Promise`. Those live in the `_NIOConcurrency` module and will move to `NIOCore` once Swift concurrency is released.
33+
To allow an easy transition to async code, SwiftNIO offers a number of helper methods on `EventLoopFuture` and `-Promise`.
5534

5635
On every `EventLoopFuture` you can call `.get()` to transition the future into an `await`-able invocation. If you want to translate async/await calls to an `EventLoopFuture` we recommend the following pattern:
5736

5837
```swift
59-
#if swift(>=5.5)
38+
#if compiler(>=5.5) && canImport(_Concurrency)
39+
6040
func yourAsyncFunctionConvertedToAFuture(on eventLoop: EventLoop)
6141
-> EventLoopFuture<Result> {
6242
let promise = context.eventLoop.makePromise(of: Out.self)
@@ -70,6 +50,28 @@ func yourAsyncFunctionConvertedToAFuture(on eventLoop: EventLoop)
7050

7151
Further helpers exist for `EventLoopGroup`, `Channel`, `ChannelOutboundInvoker` and `ChannelPipeline`.
7252

53+
54+
### `#if` guarding code using Concurrency
55+
56+
In order to have code using concurrency along with code not using concurrency, you may have to `#if` guard certain pieces of code. The correct way to do so is the following:
57+
58+
```swift
59+
#if compiler(>=5.5) && canImport(_Concurrency)
60+
...
61+
#endif
62+
```
63+
64+
Please note that you do _not_ need to _import_ the `_Concurrency` at all, if it is present it is imported automatically.
65+
66+
```swift
67+
#if compiler(>=5.5) && canImport(_Concurrency)
68+
// DO NOT DO THIS.
69+
// Instead don't do any import and it'll import automatically when possible.
70+
import _Concurrency
71+
#endif
72+
```
73+
74+
7375
### Sendable Checking
7476

7577
> [SE-0302][SE-0302] introduced the `Sendable` protocol, which is used to indicate which types have values that can safely be copied across actors or, more generally, into any context where a copy of the value might be used concurrently with the original. Applied uniformly to all Swift code, `Sendable` checking eliminates a large class of data races caused by shared mutable state.
@@ -110,18 +112,20 @@ Here, the `Value` type must be marked `Sendable` for Swift 6’s concurrency che
110112
In such situations, it may be helpful to utilize the following trick to be able to share the same `Container` declaration between both Swift versions of the library:
111113

112114
```swift
113-
#if compiler(>=5.5)
115+
#if swift(>=5.5) && canImport(_Concurrency)
114116
public typealias MYPREFIX_Sendable = Swift.Sendable
115117
#else
116118
public typealias MYPREFIX_Sendable = Any
117119
#endif
118120
```
119121

122+
> **NOTE:** Yes, we're using `swift(>=5.5)` here, while we're using `compiler(>=5.5)` to guard specific APIs using conrrency features.
123+
120124
The `Any` alias is effectively a no-op when applied as generic constraint, and thus this way it is possible to keep the same `Container<Value>` declaration working across Swift versions.
121125

122126
### Task Local Values and Logging
123127

124-
The newly introduced Task Local Values API ([SE-0311][SE-0311]) allows for implicit carrying of metadata along with `Task` execution. It is a natural fit for for tracing and carrying metadata around with task execution, and e.g. including it in log messages.
128+
The newly introduced Task Local Values API ([SE-0311][SE-0311]) allows for implicit carrying of metadata along with `Task` execution. It is a natural fit for tracing and carrying metadata around with task execution, and e.g. including it in log messages.
125129

126130
We are working on adjusting [SwiftLog](https://github.com/apple/swift-log) to become powerful enough to automatically pick up and log specific task local values. This change will be introduced in a source compatible way.
127131

@@ -170,31 +174,35 @@ While we expect potential performance gains from using custom executors “on th
170174
The guidance here will evolve as Swift Evolution proposals for Custom Executors are proposed, but don’t hold off adopting Swift Concurrency until custom executors “land” - it is important to start adoption early. For most code we believe that the gains from adopting Swift Concurrency vastly outweigh the slight performance cost actor-hops might induce.
171175

172176

173-
### Reduce use of Swift NIO Futures as “Concurrency Library“
177+
### Reduce use of SwiftNIO Futures as “Concurrency Library“
174178

175-
Swift NIO currently provides a number of concurrency types for the Swift on Server ecosystem. Most notably `EventLoopFuture`s and `EventLoopPromise`s, that are used widely for asynchronous results. While the SSWG recommended using those at the API level in the past for easier interplay of server libraries, we advise to deprecate or remove such APIs once Swift 6 lands. The swift-server ecosystem should go all in on the structured concurrency features the languages provides. For this reason, it is crucial to provide async/await APIs today, to give your library users time to adopt the new APIs.
179+
SwiftNIO currently provides a number of concurrency types for the Swift on Server ecosystem. Most notably `EventLoopFuture`s and `EventLoopPromise`s, that are used widely for asynchronous results. While the SSWG recommended using those at the API level in the past for easier interplay of server libraries, we advise to deprecate or remove such APIs once Swift 6 lands. The swift-server ecosystem should go all in on the structured concurrency features the languages provides. For this reason, it is crucial to provide async/await APIs today, to give your library users time to adopt the new APIs.
176180

177181
Some NIO types will remain however in the public interfaces of Swift on server libraries. We expect that networking clients and servers continue to be initialized with `EventLoopGroup`s. The underlying transport mechanism (`NIOPosix` and `NIOTransportServices`) should become implementation details however and should not be exposed to library adopters.
178182

179183
### SwiftNIO 3
180184

181-
While subject to change, it is likely that Swift NIO will cut a 3.0 release in the months after Swift 6.0, at which point in time Swift will have enabled “full” `Sendable` checking.
185+
While subject to change, it is likely that SwiftNIO will cut a 3.0 release in the months after Swift 6.0, at which point in time Swift will have enabled “full” `Sendable` checking.
186+
187+
You should not expect NIO to suddenly become “more async”, NIO’s inherent design principles are about performing small tasks on the event loop and using Futures for any async operations. The design of NIO is not expected to change. Channel pipelines are not expected to become "async" in the Swift Concurrency meaning of the word. This is because SwiftNIO is, at its heard, an IO system, and that poses a challenge to the co-operative, shared, thread-pool used by Swift Concurrency. This thread pool must not be blocked by any operation, because doing so will starve the pool and prevent further progress of other async tasks.
188+
189+
I/O systems however must, at some point, block a thread waiting for more I/O events, either in an I/O syscall or in something like epoll_wait. This is how NIO works: each of the event loop threads ultimately blocks on epoll_wait. We can’t do that inside the cooperative thread pool, as to do so would starve it for other async tasks, so we’d have to do so on a different thread. As such, SwiftNIO should not be used _on_ the cooperative threadpool, but should take ownership and full control of its threads–because it is an I/O system.
182190

183-
Do not expect NIO to suddenly become “more async”, NIO’s inherent design principles are about performing small tasks on the event loop and using Futures for any async operations. The design of NIO is not expected to change. It is crucial to its high performance networking design. Channel pipelines are not expected to become “async”.
191+
It would be possible to make all NIO work happen on the co-operative pool, and thread-hop between each I/O operation and dispatching it onto the async/await pool, however this is not acceptable for high performance I/O: the context switch for _each I/O operation_ is too expensive. As a result, SwiftNIO is not planning to just adopt Swift Concurrency for the ease of use it brings, because in its specific context, the context switches are not an acceptable tradeoff. SwiftNIO could however cooperate with Swift Concurrency with the arrival of "custom executors" in the language runtime, however this has not been fully proposed yet, so we are not going to speculate about this too much.
184192

185193
The NIO team will however use the chance to remove deprecated APIs and improve some APIs. The scope of changes should be comparable to the NIO1 → NIO2 version bump. If your SwiftNIO code compiles today without warnings, chances are high that it will continue to work without modifications in NIO3.
186194

187195
After the release of NIO3, NIO2 will see bug fixes only.
188196

189197
### End-user code breakage
190198

191-
It is expected that Swift 6 will break some code. As mentioned Swift NIO 3 is also going to be released sometime around Swift 6 dropping. Keeping this in mind, it might be a good idea to align major version releases around the same time, along with updating version requirements to Swift 6 and NIO 3 in your libraries.
199+
It is expected that Swift 6 will break some code. As mentioned SwiftNIO 3 is also going to be released sometime around Swift 6 dropping. Keeping this in mind, it might be a good idea to align major version releases around the same time, along with updating version requirements to Swift 6 and NIO 3 in your libraries.
192200

193-
Both Swift and Swift NIO are not planning to do “vast amounts of change”, so adoption should be possible without major pains.
201+
Both Swift and SwiftNIO are not planning to do “vast amounts of change”, so adoption should be possible without major pains.
194202

195203
### Guidance for library users
196204

197-
As soon as Swift 6 comes out, we recommend using the latest Swift 6 toolchains, even if using the Swift 5.5.n language mode (which may yield only warnings rather than hard failures on failed Sendability checks).
205+
As soon as Swift 6 comes out, we recommend using the latest Swift 6 toolchains, even if using the Swift 5.5.n language mode (which may yield only warnings rather than hard failures on failed Sendability checks). This will result in better warnings and compiler hints, than just using a 5.5 toolchain.
198206

199207
[sendable-staging]: https://github.com/DougGregor/swift-evolution/blob/sendable-staging/proposals/nnnn-sendable-staging.md
200208
[SE-0302]: https://github.com/apple/swift-evolution/blob/main/proposals/0302-concurrent-value-and-concurrent-closures.md

0 commit comments

Comments
 (0)