Skip to content

Commit a553108

Browse files
committed
Added reference documentation for the available attribute's noasync argument.
1 parent e0493a4 commit a553108

File tree

1 file changed

+98
-1
lines changed

1 file changed

+98
-1
lines changed

TSPL.docc/ReferenceManual/Attributes.md

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,105 @@ including important milestones.
210210
obsoleted: <#version number#>
211211
```
212212
The *version number* consists of one to three positive integers, separated by periods.
213+
214+
- The `noasync` argument indicates that the declared symbol can't be used directly
215+
in an asynchronous context.
216+
217+
Because Swift concurrency can resume on a different thread
218+
after a potential suspension point,
219+
you can't use elements like thread-local storage, locks, mutexes, and semaphores
220+
across suspension points. For example, accessing thread-local storage
221+
on both sides of a suspension point could produce unintended behaviors.
222+
223+
```swift
224+
let customThreadLog = "CustomThreadLog"
225+
226+
// Reads the log for the current thread.
227+
func readLog() -> String? {
228+
return Thread.current.threadDictionary[customThreadLog] as? String
229+
}
230+
231+
// Add text to the log for the current thread.
232+
func appendToLog(_ text: String) {
233+
var log = readLog() ?? ""
234+
log.append(text)
235+
Thread.current.threadDictionary[customThreadLog] = log
236+
}
237+
238+
// This function modifies thread-local storage across a
239+
// potential suspension point.
240+
func verify(name: String) async {
241+
// This call appends the text to the current thread's log.
242+
appendToLog("Verifying \(name).\n")
243+
244+
// This is a potential suspension point.
245+
let result = await coin.flip()
246+
247+
// This call might append the text to a different thread's log.
248+
appendToLog("\(name) \(result ? "is" : "isn't") verified!\n\n")
249+
}
250+
```
251+
252+
In the previous code listing,
253+
the compiler doesn't have enough information to detect the problem.
254+
To prevent issues like this,
255+
add an `@available(*, noasync)` attribute to the symbol's declaration:
256+
257+
```swift
258+
// Reads the name for the current thread.
259+
@available(*, noasync)
260+
func readLog() -> String? {
261+
return Thread.current.threadDictionary[customThreadLog] as? String
262+
}
263+
```
264+
265+
This attribute tells the compiler to raise a build error
266+
when the symbol is used in an asynchronous context.
267+
You can also use the `message` attribute to provide additional information
268+
about the symbol.
269+
270+
```swift
271+
// Reads the name for the current thread.
272+
@available(*, noasync, message: "Use safeVerify(name:) instead.")
273+
func readLog() -> String? {
274+
return Thread.current.threadDictionary[customThreadLog] as? String
275+
}
276+
```
277+
278+
While the `noasync` argument prevents accidental, unsafe use of a symbol,
279+
it can also prevent safe uses.
280+
In many cases, `nonasync` symbols can still be used safely in specific situations.
281+
If you can ensure that a particular use is safe in an asynchronous context,
282+
wrap the symbol in a safe, synchronous function.
283+
You can then call that synchronous wrapper from your asynchronous context.
284+
The compiler won't raise an error, because the `noasync` symbol
285+
isn't used directly in an asynchronous context.
286+
287+
```swift
288+
// A synchronous wrapper function around the noasync functions
289+
// guarantees that there won't be a potential suspension point
290+
// between the appendToLog() calls.
291+
func safeVerify(name: String) -> String {
292+
appendToLog("Verifying \(name).\n")
293+
294+
// This is no longer a potential suspension point.
295+
let result = coin.synchronousFlip()
296+
297+
appendToLog("\(name) \(result ? "is" : "isn't") verified!\n\n")
298+
299+
// Multiple calls to safeVerify() may not occur on the same thread.
300+
// Return the log for the current thread.
301+
return readLog() ?? "No log found."
302+
}
303+
```
304+
305+
You can use the `noasync` argument with most declarations;
306+
however, you can't use it when declaring destructors,
307+
because the system must be able to call destructors from any context.
308+
213309
- The `message` argument provides a textual message that the compiler displays
214-
when emitting a warning or error about the use of a deprecated or obsoleted declaration.
310+
when emitting a warning or error about the use of a deprecated, obsoleted,
311+
or noasync declaration.
215312
It has the following form:
216313

217314
```swift

0 commit comments

Comments
 (0)