Skip to content

Commit e3e9569

Browse files
authored
Merge pull request #140 from ktoso/wip-swift-6-concurrency-checking
2 parents e9ca4f7 + 987b1b5 commit e3e9569

File tree

4 files changed

+133
-37
lines changed

4 files changed

+133
-37
lines changed

.licenseignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
*.json
1818
Package.swift
1919
**/Package.swift
20-
Package@-*.swift
20+
Package@swift*.swift
2121
**/Package@-*.swift
2222
Package.resolved
2323
**/Package.resolved

[email protected]

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// swift-tools-version:6.0
2+
//===----------------------------------------------------------------------===//
3+
//
4+
// This source file is part of the SwiftPrometheus open source project
5+
//
6+
// Copyright (c) 2018-2025 SwiftPrometheus project authors
7+
// Licensed under Apache License v2.0
8+
//
9+
// See LICENSE.txt for license information
10+
// See CONTRIBUTORS.txt for the list of SwiftPrometheus project authors
11+
//
12+
// SPDX-License-Identifier: Apache-2.0
13+
//
14+
//===----------------------------------------------------------------------===//
15+
16+
import PackageDescription
17+
18+
let package = Package(
19+
name: "swift-prometheus",
20+
platforms: [.macOS(.v13), .iOS(.v16), .watchOS(.v9), .tvOS(.v16)],
21+
products: [
22+
.library(
23+
name: "Prometheus",
24+
targets: ["Prometheus"]
25+
)
26+
],
27+
dependencies: [
28+
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"),
29+
.package(url: "https://github.com/apple/swift-metrics.git", from: "2.4.1"),
30+
],
31+
targets: [
32+
.target(
33+
name: "Prometheus",
34+
dependencies: [
35+
.product(name: "Atomics", package: "swift-atomics"),
36+
.product(name: "CoreMetrics", package: "swift-metrics"),
37+
]
38+
),
39+
.testTarget(
40+
name: "PrometheusTests",
41+
dependencies: [
42+
"Prometheus"
43+
]
44+
),
45+
]
46+
)
47+
48+
for target in package.targets {
49+
var settings = target.swiftSettings ?? []
50+
settings.append(.enableExperimentalFeature("StrictConcurrency=complete"))
51+
target.swiftSettings = settings
52+
}

Sources/Prometheus/NIOLock.swift

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,16 @@ import Darwin
3131
import ucrt
3232
import WinSDK
3333
#elseif canImport(Glibc)
34-
import Glibc
34+
@preconcurrency import Glibc
3535
#elseif canImport(Musl)
36-
import Musl
36+
@preconcurrency import Musl
37+
#elseif canImport(Bionic)
38+
@preconcurrency import Bionic
39+
#elseif canImport(WASILibc)
40+
@preconcurrency import WASILibc
41+
#if canImport(wasi_pthread)
42+
import wasi_pthread
43+
#endif
3744
#else
3845
#error("The concurrency NIOLock module was unable to identify your C library.")
3946
#endif
@@ -47,7 +54,7 @@ typealias LockPrimitive = pthread_mutex_t
4754
#endif
4855

4956
@usableFromInline
50-
enum LockOperations {}
57+
enum LockOperations: Sendable {}
5158

5259
extension LockOperations {
5360
@inlinable
@@ -56,12 +63,15 @@ extension LockOperations {
5663

5764
#if os(Windows)
5865
InitializeSRWLock(mutex)
59-
#else
66+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
6067
var attr = pthread_mutexattr_t()
6168
pthread_mutexattr_init(&attr)
62-
debugOnly {
63-
pthread_mutexattr_settype(&attr, .init(PTHREAD_MUTEX_ERRORCHECK))
64-
}
69+
assert(
70+
{
71+
pthread_mutexattr_settype(&attr, .init(PTHREAD_MUTEX_ERRORCHECK))
72+
return true
73+
}()
74+
)
6575

6676
let err = pthread_mutex_init(mutex, &attr)
6777
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
@@ -74,7 +84,7 @@ extension LockOperations {
7484

7585
#if os(Windows)
7686
// SRWLOCK does not need to be free'd
77-
#else
87+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
7888
let err = pthread_mutex_destroy(mutex)
7989
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
8090
#endif
@@ -86,7 +96,7 @@ extension LockOperations {
8696

8797
#if os(Windows)
8898
AcquireSRWLockExclusive(mutex)
89-
#else
99+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
90100
let err = pthread_mutex_lock(mutex)
91101
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
92102
#endif
@@ -98,7 +108,7 @@ extension LockOperations {
98108

99109
#if os(Windows)
100110
ReleaseSRWLockExclusive(mutex)
101-
#else
111+
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
102112
let err = pthread_mutex_unlock(mutex)
103113
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
104114
#endif
@@ -139,9 +149,11 @@ final class LockStorage<Value>: ManagedBuffer<Value, LockPrimitive> {
139149
@inlinable
140150
static func create(value: Value) -> Self {
141151
let buffer = Self.create(minimumCapacity: 1) { _ in
142-
return value
152+
value
143153
}
144-
// Avoid 'unsafeDowncast' as there is a miscompilation on 5.10.
154+
// Intentionally using a force cast here to avoid a miss compiliation in 5.10.
155+
// This is as fast as an unsafeDownCast since ManagedBuffer is inlined and the optimizer
156+
// can eliminate the upcast/downcast pair
145157
let storage = buffer as! Self
146158

147159
storage.withUnsafeMutablePointers { _, lockPtr in
@@ -175,7 +187,7 @@ final class LockStorage<Value>: ManagedBuffer<Value, LockPrimitive> {
175187
@inlinable
176188
func withLockPrimitive<T>(_ body: (UnsafeMutablePointer<LockPrimitive>) throws -> T) rethrows -> T {
177189
try self.withUnsafeMutablePointerToElements { lockPtr in
178-
return try body(lockPtr)
190+
try body(lockPtr)
179191
}
180192
}
181193

@@ -189,11 +201,16 @@ final class LockStorage<Value>: ManagedBuffer<Value, LockPrimitive> {
189201
}
190202
}
191203

192-
extension LockStorage: @unchecked Sendable {}
204+
// This compiler guard is here becaue `ManagedBuffer` is already declaring
205+
// Sendable unavailability after 6.1, which `LockStorage` inherits.
206+
#if compiler(<6.2)
207+
@available(*, unavailable)
208+
extension LockStorage: Sendable {}
209+
#endif
193210

194211
/// A threading lock based on `libpthread` instead of `libdispatch`.
195212
///
196-
/// - note: ``NIOLock`` has reference semantics.
213+
/// - Note: ``NIOLock`` has reference semantics.
197214
///
198215
/// This object provides a lock on top of a single `pthread_mutex_t`. This kind
199216
/// of lock is safe to use with `libpthread`-based threading models, such as the
@@ -229,7 +246,7 @@ struct NIOLock {
229246

230247
@inlinable
231248
internal func withLockPrimitive<T>(_ body: (UnsafeMutablePointer<LockPrimitive>) throws -> T) rethrows -> T {
232-
return try self._storage.withLockPrimitive(body)
249+
try self._storage.withLockPrimitive(body)
233250
}
234251
}
235252

@@ -257,26 +274,11 @@ extension NIOLock {
257274
}
258275
}
259276

260-
extension NIOLock: Sendable {}
277+
extension NIOLock: @unchecked Sendable {}
261278

262279
extension UnsafeMutablePointer {
263280
@inlinable
264281
func assertValidAlignment() {
265282
assert(UInt(bitPattern: self) % UInt(MemoryLayout<Pointee>.alignment) == 0)
266283
}
267284
}
268-
269-
/// A utility function that runs the body code only in debug builds, without
270-
/// emitting compiler warnings.
271-
///
272-
/// This is currently the only way to do this in Swift: see
273-
/// https://forums.swift.org/t/support-debug-only-code/11037 for a discussion.
274-
@inlinable
275-
internal func debugOnly(_ body: () -> Void) {
276-
assert(
277-
{
278-
body()
279-
return true
280-
}()
281-
)
282-
}

Sources/Prometheus/NIOLockedValueBox.swift

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727

2828
/// Provides locked access to `Value`.
2929
///
30-
/// - note: ``NIOLockedValueBox`` has reference semantics and holds the `Value`
30+
/// - Note: ``NIOLockedValueBox`` has reference semantics and holds the `Value`
3131
/// alongside a lock behind a reference.
3232
///
33-
/// This is no different than creating a ``Lock`` and protecting all
33+
/// This is no different than creating a `Lock` and protecting all
3434
/// accesses to a value using the lock. But it's easy to forget to actually
3535
/// acquire/release the lock in the correct place. ``NIOLockedValueBox`` makes
3636
/// that much easier.
@@ -49,8 +49,50 @@ struct NIOLockedValueBox<Value> {
4949
/// Access the `Value`, allowing mutation of it.
5050
@inlinable
5151
func withLockedValue<T>(_ mutate: (inout Value) throws -> T) rethrows -> T {
52-
return try self._storage.withLockedValue(mutate)
52+
try self._storage.withLockedValue(mutate)
53+
}
54+
55+
/// Provides an unsafe view over the lock and its value.
56+
///
57+
/// This can be beneficial when you require fine grained control over the lock in some
58+
/// situations but don't want lose the benefits of ``withLockedValue(_:)`` in others by
59+
/// switching to ``NIOLock``.
60+
var unsafe: Unsafe {
61+
Unsafe(_storage: self._storage)
62+
}
63+
64+
/// Provides an unsafe view over the lock and its value.
65+
struct Unsafe {
66+
@usableFromInline
67+
let _storage: LockStorage<Value>
68+
69+
/// Manually acquire the lock.
70+
@inlinable
71+
func lock() {
72+
self._storage.lock()
73+
}
74+
75+
/// Manually release the lock.
76+
@inlinable
77+
func unlock() {
78+
self._storage.unlock()
79+
}
80+
81+
/// Mutate the value, assuming the lock has been acquired manually.
82+
///
83+
/// - Parameter mutate: A closure with scoped access to the value.
84+
/// - Returns: The result of the `mutate` closure.
85+
@inlinable
86+
func withValueAssumingLockIsAcquired<Result>(
87+
_ mutate: (_ value: inout Value) throws -> Result
88+
) rethrows -> Result {
89+
try self._storage.withUnsafeMutablePointerToHeader { value in
90+
try mutate(&value.pointee)
91+
}
92+
}
5393
}
5494
}
5595

56-
extension NIOLockedValueBox: Sendable where Value: Sendable {}
96+
extension NIOLockedValueBox: @unchecked Sendable where Value: Sendable {}
97+
98+
extension NIOLockedValueBox.Unsafe: @unchecked Sendable where Value: Sendable {}

0 commit comments

Comments
 (0)