From f826495f54c91d4fb2e1d1c4a7f3aac13d21b5a6 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 13 Aug 2024 09:54:50 -0700 Subject: [PATCH 01/12] Mutex back-port Swift's new `Mutex` type cannot be back-deployed at this time, so we can provide our own implementation with matching API. --- Package@swift-6.0.swift | 7 ++- Sources/ConcurrencyExtras/Mutex.swift | 80 +++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 Sources/ConcurrencyExtras/Mutex.swift diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index 67c847f..1f03ed1 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -18,7 +18,10 @@ let package = Package( ], targets: [ .target( - name: "ConcurrencyExtras" + name: "ConcurrencyExtras", + swiftSettings: [ + .enableExperimentalFeature("StaticExclusiveOnly"), + ] ), .testTarget( name: "ConcurrencyExtrasTests", @@ -27,7 +30,7 @@ let package = Package( ] ), ], - swiftLanguageVersions: [.v6] + swiftLanguageModes: [.v6] ) #if !os(Windows) diff --git a/Sources/ConcurrencyExtras/Mutex.swift b/Sources/ConcurrencyExtras/Mutex.swift new file mode 100644 index 0000000..91a2c66 --- /dev/null +++ b/Sources/ConcurrencyExtras/Mutex.swift @@ -0,0 +1,80 @@ +#if compiler(>=6) + import Foundation + + /// A synchronization primitive that protects shared mutable state via mutual exclusion. + /// + /// A back-port of Swift's `Mutex` type for wider platform availability. + @frozen + @_staticExclusiveOnly + @available(iOS, obsoleted: 18, message: "Use 'Synchronization.Mutex', instead.") + @available(macOS, obsoleted: 15, message: "Use 'Synchronization.Mutex', instead.") + @available(tvOS, obsoleted: 18, message: "Use 'Synchronization.Mutex', instead.") + @available(visionOS, obsoleted: 2, message: "Use 'Synchronization.Mutex', instead.") + @available(watchOS, obsoleted: 11, message: "Use 'Synchronization.Mutex', instead.") + public struct Mutex: ~Copyable { + @usableFromInline + let lock = NSLock() + + @usableFromInline + let box: Box + + /// Initializes a value of this mutex with the given initial state. + /// + /// - Parameter initialValue: The initial value to give to the mutex. + @_transparent + public init(_ initialValue: consuming sending Value) { + box = Box(initialValue) + } + + @usableFromInline + final class Box { + @usableFromInline + var value: Value + @usableFromInline + init(_ initialValue: consuming sending Value) { + value = initialValue + } + } + } + + extension Mutex: @unchecked Sendable where Value: ~Copyable {} + + extension Mutex where Value: ~Copyable { + /// Calls the given closure after acquiring the lock and then releases ownership. + @_transparent + public borrowing func withLock( + _ body: (inout sending Value) throws(E) -> sending Result + ) throws(E) -> sending Result { + lock.lock() + defer { lock.unlock() } + return try body(&box.value) + } + + /// Attempts to acquire the lock and then calls the given closure if successful. + @_transparent + public borrowing func withLockIfAvailable( + _ body: (inout sending Value) throws(E) -> sending Result + ) throws(E) -> sending Result? { + guard lock.try() else { return nil } + defer { lock.unlock() } + return try body(&box.value) + } + } + + extension Mutex where Value == Void { + @_transparent + public borrowing func _unsafeLock() { + lock.lock() + } + + @_transparent + public borrowing func _unsafeTryLock() -> Bool { + lock.try() + } + + @_transparent + public borrowing func _unsafeUnlock() { + lock.unlock() + } + } +#endif From 663966374e637926e46630fd4f06ee916ab3a735 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 13 Aug 2024 09:58:16 -0700 Subject: [PATCH 02/12] Soft-deprecate `LockIsolated` --- Sources/ConcurrencyExtras/LockIsolated.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/ConcurrencyExtras/LockIsolated.swift b/Sources/ConcurrencyExtras/LockIsolated.swift index 640d88c..8237594 100644 --- a/Sources/ConcurrencyExtras/LockIsolated.swift +++ b/Sources/ConcurrencyExtras/LockIsolated.swift @@ -4,6 +4,12 @@ import Foundation /// /// If you trust the sendability of the underlying value, consider using ``UncheckedSendable``, /// instead. +#if compiler(>=6) + @available(iOS, deprecated: 10000, message: "Use 'Mutex', instead.") + @available(macOS, deprecated: 10000, message: "Use 'Mutex', instead.") + @available(tvOS, deprecated: 10000, message: "Use 'Mutex', instead.") + @available(watchOS, deprecated: 10000, message: "Use 'Mutex', instead.") +#endif @dynamicMemberLookup public final class LockIsolated: @unchecked Sendable { private var _value: Value From 62f61afcd189d55e157489060867716cc0ebfbbf Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 13 Aug 2024 10:03:31 -0700 Subject: [PATCH 03/12] wip --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e96a1f4..1c77a5f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: - uses: bytecodealliance/actions/wasmtime/setup@v1 - uses: swiftwasm/setup-swiftwasm@v1 with: - swift-version: "wasm-5.9.2-RELEASE" + swift-version: "wasm-6.0-SNAPSHOT-2024-07-20-a" - name: Build tests run: swift build --triple wasm32-unknown-wasi --build-tests - name: Run tests From b51670ea2d10c56699857255f03d8e5fa87c620a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 13 Aug 2024 10:05:16 -0700 Subject: [PATCH 04/12] wip --- Package@swift-6.0.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index 1f03ed1..4b997bf 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -30,7 +30,7 @@ let package = Package( ] ), ], - swiftLanguageModes: [.v6] + swiftLanguageVersions: [.v6] ) #if !os(Windows) From b776539408262de4504b0d37c294592f5be9fc4c Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 13 Aug 2024 10:08:50 -0700 Subject: [PATCH 05/12] wip --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c77a5f..000935b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: - uses: bytecodealliance/actions/wasmtime/setup@v1 - uses: swiftwasm/setup-swiftwasm@v1 with: - swift-version: "wasm-6.0-SNAPSHOT-2024-07-20-a" + swift-version: "wasm-DEVELOPMENT-SNAPSHOT-2024-08-11-a" - name: Build tests run: swift build --triple wasm32-unknown-wasi --build-tests - name: Run tests From 89fc507f3870b869fad6342a081c7fe4fe89d711 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 13 Aug 2024 10:12:55 -0700 Subject: [PATCH 06/12] wip --- .github/workflows/ci.yml | 2 +- Package@swift-6.0.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 000935b..285d5d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: with: swift-version: "wasm-DEVELOPMENT-SNAPSHOT-2024-08-11-a" - name: Build tests - run: swift build --triple wasm32-unknown-wasi --build-tests + run: swift build --triple wasm32-unknown-wasi --build-tests --static-swift-stdlib - name: Run tests run: wasmtime --dir . .build/debug/swift-concurrency-extrasPackageTests.wasm diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index 4b997bf..1f03ed1 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -30,7 +30,7 @@ let package = Package( ] ), ], - swiftLanguageVersions: [.v6] + swiftLanguageModes: [.v6] ) #if !os(Windows) From 1f2ff32c5d48c04bdc6dbc9c40969cdc37dfb6f9 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 13 Aug 2024 10:16:13 -0700 Subject: [PATCH 07/12] wip --- Sources/ConcurrencyExtras/Mutex.swift | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/Sources/ConcurrencyExtras/Mutex.swift b/Sources/ConcurrencyExtras/Mutex.swift index 91a2c66..8fc25c3 100644 --- a/Sources/ConcurrencyExtras/Mutex.swift +++ b/Sources/ConcurrencyExtras/Mutex.swift @@ -4,7 +4,6 @@ /// A synchronization primitive that protects shared mutable state via mutual exclusion. /// /// A back-port of Swift's `Mutex` type for wider platform availability. - @frozen @_staticExclusiveOnly @available(iOS, obsoleted: 18, message: "Use 'Synchronization.Mutex', instead.") @available(macOS, obsoleted: 15, message: "Use 'Synchronization.Mutex', instead.") @@ -12,25 +11,18 @@ @available(visionOS, obsoleted: 2, message: "Use 'Synchronization.Mutex', instead.") @available(watchOS, obsoleted: 11, message: "Use 'Synchronization.Mutex', instead.") public struct Mutex: ~Copyable { - @usableFromInline - let lock = NSLock() - - @usableFromInline - let box: Box + private let lock = NSLock() + private let box: Box /// Initializes a value of this mutex with the given initial state. /// /// - Parameter initialValue: The initial value to give to the mutex. - @_transparent public init(_ initialValue: consuming sending Value) { box = Box(initialValue) } - @usableFromInline - final class Box { - @usableFromInline + private final class Box { var value: Value - @usableFromInline init(_ initialValue: consuming sending Value) { value = initialValue } @@ -41,7 +33,6 @@ extension Mutex where Value: ~Copyable { /// Calls the given closure after acquiring the lock and then releases ownership. - @_transparent public borrowing func withLock( _ body: (inout sending Value) throws(E) -> sending Result ) throws(E) -> sending Result { @@ -51,7 +42,6 @@ } /// Attempts to acquire the lock and then calls the given closure if successful. - @_transparent public borrowing func withLockIfAvailable( _ body: (inout sending Value) throws(E) -> sending Result ) throws(E) -> sending Result? { @@ -62,17 +52,14 @@ } extension Mutex where Value == Void { - @_transparent public borrowing func _unsafeLock() { lock.lock() } - @_transparent public borrowing func _unsafeTryLock() -> Bool { lock.try() } - @_transparent public borrowing func _unsafeUnlock() { lock.unlock() } From db7952eced65af4028695318103b62ace0e17047 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 13 Aug 2024 10:18:49 -0700 Subject: [PATCH 08/12] wip --- Sources/ConcurrencyExtras/Mutex.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ConcurrencyExtras/Mutex.swift b/Sources/ConcurrencyExtras/Mutex.swift index 8fc25c3..a9428c3 100644 --- a/Sources/ConcurrencyExtras/Mutex.swift +++ b/Sources/ConcurrencyExtras/Mutex.swift @@ -1,4 +1,4 @@ -#if compiler(>=6) +#if compiler(>=6) && canImport(Darwin) import Foundation /// A synchronization primitive that protects shared mutable state via mutual exclusion. From 508650eae699d782176fc7c66c02e316828a4592 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 13 Aug 2024 10:19:06 -0700 Subject: [PATCH 09/12] Revert "wip" This reverts commit 1f2ff32c5d48c04bdc6dbc9c40969cdc37dfb6f9. --- Sources/ConcurrencyExtras/Mutex.swift | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Sources/ConcurrencyExtras/Mutex.swift b/Sources/ConcurrencyExtras/Mutex.swift index a9428c3..34a08fa 100644 --- a/Sources/ConcurrencyExtras/Mutex.swift +++ b/Sources/ConcurrencyExtras/Mutex.swift @@ -4,6 +4,7 @@ /// A synchronization primitive that protects shared mutable state via mutual exclusion. /// /// A back-port of Swift's `Mutex` type for wider platform availability. + @frozen @_staticExclusiveOnly @available(iOS, obsoleted: 18, message: "Use 'Synchronization.Mutex', instead.") @available(macOS, obsoleted: 15, message: "Use 'Synchronization.Mutex', instead.") @@ -11,18 +12,25 @@ @available(visionOS, obsoleted: 2, message: "Use 'Synchronization.Mutex', instead.") @available(watchOS, obsoleted: 11, message: "Use 'Synchronization.Mutex', instead.") public struct Mutex: ~Copyable { - private let lock = NSLock() - private let box: Box + @usableFromInline + let lock = NSLock() + + @usableFromInline + let box: Box /// Initializes a value of this mutex with the given initial state. /// /// - Parameter initialValue: The initial value to give to the mutex. + @_transparent public init(_ initialValue: consuming sending Value) { box = Box(initialValue) } - private final class Box { + @usableFromInline + final class Box { + @usableFromInline var value: Value + @usableFromInline init(_ initialValue: consuming sending Value) { value = initialValue } @@ -33,6 +41,7 @@ extension Mutex where Value: ~Copyable { /// Calls the given closure after acquiring the lock and then releases ownership. + @_transparent public borrowing func withLock( _ body: (inout sending Value) throws(E) -> sending Result ) throws(E) -> sending Result { @@ -42,6 +51,7 @@ } /// Attempts to acquire the lock and then calls the given closure if successful. + @_transparent public borrowing func withLockIfAvailable( _ body: (inout sending Value) throws(E) -> sending Result ) throws(E) -> sending Result? { @@ -52,14 +62,17 @@ } extension Mutex where Value == Void { + @_transparent public borrowing func _unsafeLock() { lock.lock() } + @_transparent public borrowing func _unsafeTryLock() -> Bool { lock.try() } + @_transparent public borrowing func _unsafeUnlock() { lock.unlock() } From c4ffeb365fd9b1ee22db2dfee0c551225a6c05f4 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 13 Aug 2024 10:44:09 -0700 Subject: [PATCH 10/12] wip --- .github/workflows/ci.yml | 4 ++-- Sources/ConcurrencyExtras/Mutex.swift | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 285d5d5..e96a1f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,9 +50,9 @@ jobs: - uses: bytecodealliance/actions/wasmtime/setup@v1 - uses: swiftwasm/setup-swiftwasm@v1 with: - swift-version: "wasm-DEVELOPMENT-SNAPSHOT-2024-08-11-a" + swift-version: "wasm-5.9.2-RELEASE" - name: Build tests - run: swift build --triple wasm32-unknown-wasi --build-tests --static-swift-stdlib + run: swift build --triple wasm32-unknown-wasi --build-tests - name: Run tests run: wasmtime --dir . .build/debug/swift-concurrency-extrasPackageTests.wasm diff --git a/Sources/ConcurrencyExtras/Mutex.swift b/Sources/ConcurrencyExtras/Mutex.swift index 34a08fa..96e12eb 100644 --- a/Sources/ConcurrencyExtras/Mutex.swift +++ b/Sources/ConcurrencyExtras/Mutex.swift @@ -13,17 +13,17 @@ @available(watchOS, obsoleted: 11, message: "Use 'Synchronization.Mutex', instead.") public struct Mutex: ~Copyable { @usableFromInline - let lock = NSLock() + let _lock = NSLock() @usableFromInline - let box: Box + let _box: Box /// Initializes a value of this mutex with the given initial state. /// /// - Parameter initialValue: The initial value to give to the mutex. @_transparent public init(_ initialValue: consuming sending Value) { - box = Box(initialValue) + _box = Box(initialValue) } @usableFromInline @@ -45,9 +45,9 @@ public borrowing func withLock( _ body: (inout sending Value) throws(E) -> sending Result ) throws(E) -> sending Result { - lock.lock() - defer { lock.unlock() } - return try body(&box.value) + _lock.lock() + defer { _lock.unlock() } + return try body(&_box.value) } /// Attempts to acquire the lock and then calls the given closure if successful. @@ -55,26 +55,26 @@ public borrowing func withLockIfAvailable( _ body: (inout sending Value) throws(E) -> sending Result ) throws(E) -> sending Result? { - guard lock.try() else { return nil } - defer { lock.unlock() } - return try body(&box.value) + guard _lock.try() else { return nil } + defer { _lock.unlock() } + return try body(&_box.value) } } extension Mutex where Value == Void { @_transparent public borrowing func _unsafeLock() { - lock.lock() + _lock.lock() } @_transparent public borrowing func _unsafeTryLock() -> Bool { - lock.try() + _lock.try() } @_transparent public borrowing func _unsafeUnlock() { - lock.unlock() + _lock.unlock() } } #endif From 7605549630cf89b7fd34f09beccd6cdbaff82cfa Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 13 Aug 2024 10:45:03 -0700 Subject: [PATCH 11/12] wip --- Sources/ConcurrencyExtras/Mutex.swift | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/Sources/ConcurrencyExtras/Mutex.swift b/Sources/ConcurrencyExtras/Mutex.swift index 96e12eb..a6783e8 100644 --- a/Sources/ConcurrencyExtras/Mutex.swift +++ b/Sources/ConcurrencyExtras/Mutex.swift @@ -4,7 +4,6 @@ /// A synchronization primitive that protects shared mutable state via mutual exclusion. /// /// A back-port of Swift's `Mutex` type for wider platform availability. - @frozen @_staticExclusiveOnly @available(iOS, obsoleted: 18, message: "Use 'Synchronization.Mutex', instead.") @available(macOS, obsoleted: 15, message: "Use 'Synchronization.Mutex', instead.") @@ -12,25 +11,18 @@ @available(visionOS, obsoleted: 2, message: "Use 'Synchronization.Mutex', instead.") @available(watchOS, obsoleted: 11, message: "Use 'Synchronization.Mutex', instead.") public struct Mutex: ~Copyable { - @usableFromInline - let _lock = NSLock() - - @usableFromInline - let _box: Box + private let _lock = NSLock() + private let _box: Box /// Initializes a value of this mutex with the given initial state. /// /// - Parameter initialValue: The initial value to give to the mutex. - @_transparent public init(_ initialValue: consuming sending Value) { _box = Box(initialValue) } - @usableFromInline - final class Box { - @usableFromInline + private final class Box { var value: Value - @usableFromInline init(_ initialValue: consuming sending Value) { value = initialValue } @@ -41,7 +33,6 @@ extension Mutex where Value: ~Copyable { /// Calls the given closure after acquiring the lock and then releases ownership. - @_transparent public borrowing func withLock( _ body: (inout sending Value) throws(E) -> sending Result ) throws(E) -> sending Result { @@ -51,7 +42,6 @@ } /// Attempts to acquire the lock and then calls the given closure if successful. - @_transparent public borrowing func withLockIfAvailable( _ body: (inout sending Value) throws(E) -> sending Result ) throws(E) -> sending Result? { @@ -62,17 +52,14 @@ } extension Mutex where Value == Void { - @_transparent public borrowing func _unsafeLock() { _lock.lock() } - @_transparent public borrowing func _unsafeTryLock() -> Bool { _lock.try() } - @_transparent public borrowing func _unsafeUnlock() { _lock.unlock() } From 5c512cf5d9fefc4809ff141d0301d7fdda1eeaa5 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 13 Aug 2024 11:23:12 -0700 Subject: [PATCH 12/12] wip --- Sources/ConcurrencyExtras/LockIsolated.swift | 172 +++++++++++-------- 1 file changed, 99 insertions(+), 73 deletions(-) diff --git a/Sources/ConcurrencyExtras/LockIsolated.swift b/Sources/ConcurrencyExtras/LockIsolated.swift index 8237594..c0b8db1 100644 --- a/Sources/ConcurrencyExtras/LockIsolated.swift +++ b/Sources/ConcurrencyExtras/LockIsolated.swift @@ -4,90 +4,116 @@ import Foundation /// /// If you trust the sendability of the underlying value, consider using ``UncheckedSendable``, /// instead. -#if compiler(>=6) - @available(iOS, deprecated: 10000, message: "Use 'Mutex', instead.") - @available(macOS, deprecated: 10000, message: "Use 'Mutex', instead.") - @available(tvOS, deprecated: 10000, message: "Use 'Mutex', instead.") - @available(watchOS, deprecated: 10000, message: "Use 'Mutex', instead.") -#endif @dynamicMemberLookup public final class LockIsolated: @unchecked Sendable { private var _value: Value private let lock = NSRecursiveLock() - /// Initializes lock-isolated state around a value. - /// - /// - Parameter value: A value to isolate with a lock. - public init(_ value: @autoclosure @Sendable () throws -> Value) rethrows { - self._value = try value() - } + #if compiler(>=6) + /// Initializes lock-isolated state around a value. + /// + /// - Parameter value: A value to isolate with a lock. + public init(_ value: sending @autoclosure () throws -> sending Value) rethrows { + self._value = try value() + } + #else + public init(_ value: @autoclosure @Sendable () throws -> Value) rethrows { + self._value = try value() + } + #endif - public subscript(dynamicMember keyPath: KeyPath) -> Subject { - self.lock.sync { - self._value[keyPath: keyPath] + #if compiler(>=6) + public subscript(dynamicMember keyPath: KeyPath) -> sending Subject { + self.lock.sync { + self._value[keyPath: keyPath] + } } - } + #else + public subscript(dynamicMember keyPath: KeyPath) -> Subject { + self.lock.sync { + self._value[keyPath: keyPath] + } + } + #endif - /// Perform an operation with isolated access to the underlying value. - /// - /// Useful for modifying a value in a single transaction. - /// - /// ```swift - /// // Isolate an integer for concurrent read/write access: - /// var count = LockIsolated(0) - /// - /// func increment() { - /// // Safely increment it: - /// self.count.withValue { $0 += 1 } - /// } - /// ``` - /// - /// - Parameter operation: An operation to be performed on the the underlying value with a lock. - /// - Returns: The result of the operation. - public func withValue( - _ operation: @Sendable (inout Value) throws -> T - ) rethrows -> T { - try self.lock.sync { - var value = self._value - defer { self._value = value } - return try operation(&value) + #if compiler(>=6) + /// Perform an operation with isolated access to the underlying value. + /// + /// Useful for modifying a value in a single transaction. + /// + /// ```swift + /// // Isolate an integer for concurrent read/write access: + /// var count = LockIsolated(0) + /// + /// func increment() { + /// // Safely increment it: + /// self.count.withValue { $0 += 1 } + /// } + /// ``` + /// + /// - Parameter operation: An operation to be performed on the the underlying value with a lock. + /// - Returns: The result of the operation. + public func withValue( + _ operation: sending (inout sending Value) throws -> sending T + ) rethrows -> T { + try self.lock.sync { + try operation(&_value) + } } - } + #else + public func withValue( + _ operation: @Sendable (inout Value) throws -> T + ) rethrows -> T { + try self.lock.sync { + var value = self._value + defer { self._value = value } + return try operation(&value) + } + } + #endif - /// Overwrite the isolated value with a new value. - /// - /// ```swift - /// // Isolate an integer for concurrent read/write access: - /// var count = LockIsolated(0) - /// - /// func reset() { - /// // Reset it: - /// self.count.setValue(0) - /// } - /// ``` - /// - /// > Tip: Use ``withValue(_:)`` instead of ``setValue(_:)`` if the value being set is derived - /// > from the current value. That is, do this: - /// > - /// > ```swift - /// > self.count.withValue { $0 += 1 } - /// > ``` - /// > - /// > ...and not this: - /// > - /// > ```swift - /// > self.count.setValue(self.count + 1) - /// > ``` - /// > - /// > ``withValue(_:)`` isolates the entire transaction and avoids data races between reading and - /// > writing the value. - /// - /// - Parameter newValue: The value to replace the current isolated value with. - public func setValue(_ newValue: @autoclosure @Sendable () throws -> Value) rethrows { - try self.lock.sync { - self._value = try newValue() + #if compiler(>=6) + /// Overwrite the isolated value with a new value. + /// + /// ```swift + /// // Isolate an integer for concurrent read/write access: + /// var count = LockIsolated(0) + /// + /// func reset() { + /// // Reset it: + /// self.count.setValue(0) + /// } + /// ``` + /// + /// > Tip: Use ``withValue(_:)`` instead of ``setValue(_:)`` if the value being set is derived + /// > from the current value. That is, do this: + /// > + /// > ```swift + /// > self.count.withValue { $0 += 1 } + /// > ``` + /// > + /// > ...and not this: + /// > + /// > ```swift + /// > self.count.setValue(self.count + 1) + /// > ``` + /// > + /// > ``withValue(_:)`` isolates the entire transaction and avoids data races between reading and + /// > writing the value. + /// + /// - Parameter newValue: The value to replace the current isolated value with. + public func setValue(_ newValue: sending @autoclosure () throws -> sending Value) rethrows { + try self.lock.sync { + self._value = try newValue() + } } - } + #else + public func setValue(_ newValue: @autoclosure @Sendable () throws -> Value) rethrows { + try self.lock.sync { + self._value = try newValue() + } + } + #endif } extension LockIsolated where Value: Sendable {