Use of `Mutex` within the stdlib's `Concurrency` module

i was looking into a bit of refactoring within some existing stdlib code within the Concurrency module, and was wondering if the Mutex type is usable in that context. the relevant method has an availability annotation of @available(SwiftStdlib 5.1, *), and if i naively introduce Mutex, i get the typical availability errors. is it possible to use Mutex in this context? if so, what's the appropriate way to update the implementation of such code to use it? are there any 'gotchas' to be aware of with such a change?

1 Like

to make this a bit more concrete, here's the sort of changes i was experimenting with. using compiler directives like #if swift(6.0) seems to allow the code to compile, but when i try and run the unit tests, they don't appear to exercise the new code path. using the if #available conditions allows the code to compile when building the stdlib target directly, but then when running the tests it errors with something like:

swift-project/swift/stdlib/public/Concurrency/AsyncStream.swift:343:21: error: cannot find 'Mutex' in scope
342 |     if #available(SwiftStdlib 6.0, *) {
343 |       let storage = Mutex(Optional(produce))
    |                     `- error: cannot find 'Mutex' in scope
344 |       context = _Context {
345 |         return await withTaskCancellationHandler {

the lit test invocation is like:

utils/run-test --lit ../llvm-project/llvm/utils/lit/lit.py \
    ../build/Ninja-RelWithDebInfoAssert/swift-macosx-arm64/test-macosx-arm64 \
    --build=true \
    --filter="async_stream"

is this some missing configuration i need to add to the tests, or is something else going on?

I don't use CMake but seems like Synchronization is only linked on Windows currently.

1 Like

#if swift(>=6.0) also requires Swift 6 language mode. #if compiler(>=6.0) will check for the compiler version without the language mode.

1 Like

using compiler directives like #if swift(6.0) seems to allow the code to compile, but when i try and run the unit tests, they don't appear to exercise the new code path.

I assume you mean that you used if #available(SwiftStdlib 6.0, *) as you did in your code snippet, rather than #if swift(>=6.0) which accomplishes something very different. The standard library doesn't compile with -swift-version 6 so code guarded with #if swift(>=6.0) would be ignored entirely.

if #available(SwiftStdlib 6.0, *) expands to if #available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *), so whether or not the code runs would depend on whether you're executing the tests on a Mac running macOS 15.0 or later.

The fact that APIs like Mutex have availability restrictions that make their use in the implementation of the stdlib more awkward is something we've been grappling with recently. There's a PR here that implemented a short term solution where you can apply a different availability macro to stdlib APIs that need their availability artificially lowered when building that stdlib separately from the operating system that it actually gets distributed with. I have ideas about using custom availability domains to implement a more permanent solution that would address this problem and also facilitate safe use of a future ABI stable stdlib distribution on other operating systems, where the availability of stdlib APIs would not be tied to the operating system version. There's quite a bit more work to do to make that a reality though.

3 Likes

thank you both for noting this! i originally intended to use the #if compiler() condition, not the language mode one. though perhaps a compile-time-only configuration isn't the correct strategy for this sort of thing (though it's not entirely clear to me why it couldn't work).

thank you for pointing this out! is the idea that API in the Synchronization module could be updated to use the StdlibDeploymentTarget availability rather than the SwiftStdlib one it currently uses, and could then be (conditionally) referenced within, say, the implementation of the Concurrency module?

assuming that's roughly in the right direction, i've tried changing a number of the Synchronization APIs to use that new availability macro (some, but not all of them in the module). doing so allows me to successfully build the stdlib target (on macOS 15), but the unit tests still seem to fail with the cannot find 'Mutex' in scope error. so the overall behavior seems unchanged, regardless of which availability macro is used.

also curious if you have any insights into any changes that may be required for the build system – is it expected that any of the CMake configuration would also need to be changed as was hinted at upthread regarding the linking logic for Windows?

Did you update CMakeLists? Xcode also does this thing that in some cases missing the proper dependency hierarchy will still allow you to import stuff while it will give you an error in other cases.

Edit: this was the change for adding it to Windows: [Runtime][Windows] Need to link with Synchronization.lib. · swiftlang/swift@05cccf9 · GitHub

The cannot find 'Mutex' in scope is unrelated to availability - availability doesn’t play a role in name lookup. I have no idea what might be causing it but some kind of CMake misconfiguration seems plausible.

1 Like

FWIW I wouldn't expect #if compiler() to be appropriate to use here, either. The sources of the standard library modules are only designed to be built by the latest compiler. There shouldn't be any configuration in CI or otherwise where they are built with a toolchain old enough that the Synchronization module or Mutex declaration wouldn't be available. If there is an axis on which the sources need to be conditionalized, I don't think it would be on compiler version.

Can you link to a log with a concrete example of this? My best guess is that there's a stdlib build configuration where the Synchronization module simply doesn't include a Mutex type at all. If that's the case, it should really be included but marked @available(*, unavailable) instead of leaving it out.

1 Like

Yeah, as a quick solution I think adopting StdlibDeploymentTarget is fine, but ideally in the near future I would like to bump the minimum deployment target of the stdlib so that the use of this macro won't be needed for types like Atomic and Mutex.

As to why you're seeing this particular error, it's because the concurrency module does not currently depend on the Synchronization module in the build system. We will need to do this eventually. cc @etcwilde we will need to make the Synchronization module a core module and not a supplementary one very soon to adopt things like Atomic and Mutex in the concurrency sources.

3 Likes