mirror of
https://github.com/swift-server/swift-prometheus.git
synced 2026-05-03 07:32:27 +00:00
Concurrency: enable 6.0 language mode / concurrency safety
Swift 6 language mode enables complete concurrency checking and will error when races are found. Also fix a few types to be 6 language mode compatible. fix licenseignore to ignore Package-swift... fix formatting fix docs in NIOLockedValueBox
This commit is contained in:
+1
-1
@@ -17,7 +17,7 @@
|
||||
*.json
|
||||
Package.swift
|
||||
**/Package.swift
|
||||
Package@-*.swift
|
||||
Package@swift*.swift
|
||||
**/Package@-*.swift
|
||||
Package.resolved
|
||||
**/Package.resolved
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// swift-tools-version:6.0
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the SwiftPrometheus open source project
|
||||
//
|
||||
// Copyright (c) 2018-2025 SwiftPrometheus project authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// See LICENSE.txt for license information
|
||||
// See CONTRIBUTORS.txt for the list of SwiftPrometheus project authors
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "swift-prometheus",
|
||||
platforms: [.macOS(.v13), .iOS(.v16), .watchOS(.v9), .tvOS(.v16)],
|
||||
products: [
|
||||
.library(
|
||||
name: "Prometheus",
|
||||
targets: ["Prometheus"]
|
||||
)
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"),
|
||||
.package(url: "https://github.com/apple/swift-metrics.git", from: "2.4.1"),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "Prometheus",
|
||||
dependencies: [
|
||||
.product(name: "Atomics", package: "swift-atomics"),
|
||||
.product(name: "CoreMetrics", package: "swift-metrics"),
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "PrometheusTests",
|
||||
dependencies: [
|
||||
"Prometheus"
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
for target in package.targets {
|
||||
var settings = target.swiftSettings ?? []
|
||||
settings.append(.enableExperimentalFeature("StrictConcurrency=complete"))
|
||||
target.swiftSettings = settings
|
||||
}
|
||||
@@ -31,9 +31,16 @@ import Darwin
|
||||
import ucrt
|
||||
import WinSDK
|
||||
#elseif canImport(Glibc)
|
||||
import Glibc
|
||||
@preconcurrency import Glibc
|
||||
#elseif canImport(Musl)
|
||||
import Musl
|
||||
@preconcurrency import Musl
|
||||
#elseif canImport(Bionic)
|
||||
@preconcurrency import Bionic
|
||||
#elseif canImport(WASILibc)
|
||||
@preconcurrency import WASILibc
|
||||
#if canImport(wasi_pthread)
|
||||
import wasi_pthread
|
||||
#endif
|
||||
#else
|
||||
#error("The concurrency NIOLock module was unable to identify your C library.")
|
||||
#endif
|
||||
@@ -47,7 +54,7 @@ typealias LockPrimitive = pthread_mutex_t
|
||||
#endif
|
||||
|
||||
@usableFromInline
|
||||
enum LockOperations {}
|
||||
enum LockOperations: Sendable {}
|
||||
|
||||
extension LockOperations {
|
||||
@inlinable
|
||||
@@ -56,12 +63,15 @@ extension LockOperations {
|
||||
|
||||
#if os(Windows)
|
||||
InitializeSRWLock(mutex)
|
||||
#else
|
||||
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
|
||||
var attr = pthread_mutexattr_t()
|
||||
pthread_mutexattr_init(&attr)
|
||||
debugOnly {
|
||||
pthread_mutexattr_settype(&attr, .init(PTHREAD_MUTEX_ERRORCHECK))
|
||||
}
|
||||
assert(
|
||||
{
|
||||
pthread_mutexattr_settype(&attr, .init(PTHREAD_MUTEX_ERRORCHECK))
|
||||
return true
|
||||
}()
|
||||
)
|
||||
|
||||
let err = pthread_mutex_init(mutex, &attr)
|
||||
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
|
||||
@@ -74,7 +84,7 @@ extension LockOperations {
|
||||
|
||||
#if os(Windows)
|
||||
// SRWLOCK does not need to be free'd
|
||||
#else
|
||||
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
|
||||
let err = pthread_mutex_destroy(mutex)
|
||||
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
|
||||
#endif
|
||||
@@ -86,7 +96,7 @@ extension LockOperations {
|
||||
|
||||
#if os(Windows)
|
||||
AcquireSRWLockExclusive(mutex)
|
||||
#else
|
||||
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
|
||||
let err = pthread_mutex_lock(mutex)
|
||||
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
|
||||
#endif
|
||||
@@ -98,7 +108,7 @@ extension LockOperations {
|
||||
|
||||
#if os(Windows)
|
||||
ReleaseSRWLockExclusive(mutex)
|
||||
#else
|
||||
#elseif (compiler(<6.1) && !os(WASI)) || (compiler(>=6.1) && _runtime(_multithreaded))
|
||||
let err = pthread_mutex_unlock(mutex)
|
||||
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
|
||||
#endif
|
||||
@@ -139,9 +149,11 @@ final class LockStorage<Value>: ManagedBuffer<Value, LockPrimitive> {
|
||||
@inlinable
|
||||
static func create(value: Value) -> Self {
|
||||
let buffer = Self.create(minimumCapacity: 1) { _ in
|
||||
return value
|
||||
value
|
||||
}
|
||||
// Avoid 'unsafeDowncast' as there is a miscompilation on 5.10.
|
||||
// Intentionally using a force cast here to avoid a miss compiliation in 5.10.
|
||||
// This is as fast as an unsafeDownCast since ManagedBuffer is inlined and the optimizer
|
||||
// can eliminate the upcast/downcast pair
|
||||
let storage = buffer as! Self
|
||||
|
||||
storage.withUnsafeMutablePointers { _, lockPtr in
|
||||
@@ -175,7 +187,7 @@ final class LockStorage<Value>: ManagedBuffer<Value, LockPrimitive> {
|
||||
@inlinable
|
||||
func withLockPrimitive<T>(_ body: (UnsafeMutablePointer<LockPrimitive>) throws -> T) rethrows -> T {
|
||||
try self.withUnsafeMutablePointerToElements { lockPtr in
|
||||
return try body(lockPtr)
|
||||
try body(lockPtr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,23 +201,28 @@ final class LockStorage<Value>: ManagedBuffer<Value, LockPrimitive> {
|
||||
}
|
||||
}
|
||||
|
||||
extension LockStorage: @unchecked Sendable {}
|
||||
// This compiler guard is here becaue `ManagedBuffer` is already declaring
|
||||
// Sendable unavailability after 6.1, which `LockStorage` inherits.
|
||||
#if compiler(<6.2)
|
||||
@available(*, unavailable)
|
||||
extension LockStorage: Sendable {}
|
||||
#endif
|
||||
|
||||
/// A threading lock based on `libpthread` instead of `libdispatch`.
|
||||
///
|
||||
/// - note: ``NIOLock`` has reference semantics.
|
||||
/// - Note: ``NIOLock`` has reference semantics.
|
||||
///
|
||||
/// This object provides a lock on top of a single `pthread_mutex_t`. This kind
|
||||
/// of lock is safe to use with `libpthread`-based threading models, such as the
|
||||
/// one used by NIO. On Windows, the lock is based on the substantially similar
|
||||
/// `SRWLOCK` type.
|
||||
struct NIOLock {
|
||||
public struct NIOLock {
|
||||
@usableFromInline
|
||||
internal let _storage: LockStorage<Void>
|
||||
|
||||
/// Create a new lock.
|
||||
@inlinable
|
||||
init() {
|
||||
public init() {
|
||||
self._storage = .create(value: ())
|
||||
}
|
||||
|
||||
@@ -214,7 +231,7 @@ struct NIOLock {
|
||||
/// Whenever possible, consider using `withLock` instead of this method and
|
||||
/// `unlock`, to simplify lock handling.
|
||||
@inlinable
|
||||
func lock() {
|
||||
public func lock() {
|
||||
self._storage.lock()
|
||||
}
|
||||
|
||||
@@ -223,13 +240,13 @@ struct NIOLock {
|
||||
/// Whenever possible, consider using `withLock` instead of this method and
|
||||
/// `lock`, to simplify lock handling.
|
||||
@inlinable
|
||||
func unlock() {
|
||||
public func unlock() {
|
||||
self._storage.unlock()
|
||||
}
|
||||
|
||||
@inlinable
|
||||
internal func withLockPrimitive<T>(_ body: (UnsafeMutablePointer<LockPrimitive>) throws -> T) rethrows -> T {
|
||||
return try self._storage.withLockPrimitive(body)
|
||||
try self._storage.withLockPrimitive(body)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +260,7 @@ extension NIOLock {
|
||||
/// - Parameter body: The block to execute while holding the lock.
|
||||
/// - Returns: The value returned by the block.
|
||||
@inlinable
|
||||
func withLock<T>(_ body: () throws -> T) rethrows -> T {
|
||||
public func withLock<T>(_ body: () throws -> T) rethrows -> T {
|
||||
self.lock()
|
||||
defer {
|
||||
self.unlock()
|
||||
@@ -252,12 +269,12 @@ extension NIOLock {
|
||||
}
|
||||
|
||||
@inlinable
|
||||
func withLockVoid(_ body: () throws -> Void) rethrows {
|
||||
public func withLockVoid(_ body: () throws -> Void) rethrows {
|
||||
try self.withLock(body)
|
||||
}
|
||||
}
|
||||
|
||||
extension NIOLock: Sendable {}
|
||||
extension NIOLock: @unchecked Sendable {}
|
||||
|
||||
extension UnsafeMutablePointer {
|
||||
@inlinable
|
||||
@@ -265,18 +282,3 @@ extension UnsafeMutablePointer {
|
||||
assert(UInt(bitPattern: self) % UInt(MemoryLayout<Pointee>.alignment) == 0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A utility function that runs the body code only in debug builds, without
|
||||
/// emitting compiler warnings.
|
||||
///
|
||||
/// This is currently the only way to do this in Swift: see
|
||||
/// https://forums.swift.org/t/support-debug-only-code/11037 for a discussion.
|
||||
@inlinable
|
||||
internal func debugOnly(_ body: () -> Void) {
|
||||
assert(
|
||||
{
|
||||
body()
|
||||
return true
|
||||
}()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -27,30 +27,71 @@
|
||||
|
||||
/// Provides locked access to `Value`.
|
||||
///
|
||||
/// - note: ``NIOLockedValueBox`` has reference semantics and holds the `Value`
|
||||
/// - Note: ``NIOLockedValueBox`` has reference semantics and holds the `Value`
|
||||
/// alongside a lock behind a reference.
|
||||
///
|
||||
/// This is no different than creating a ``Lock`` and protecting all
|
||||
/// This is no different than creating a `Lock` and protecting all
|
||||
/// accesses to a value using the lock. But it's easy to forget to actually
|
||||
/// acquire/release the lock in the correct place. ``NIOLockedValueBox`` makes
|
||||
/// that much easier.
|
||||
@usableFromInline
|
||||
struct NIOLockedValueBox<Value> {
|
||||
public struct NIOLockedValueBox<Value> {
|
||||
|
||||
@usableFromInline
|
||||
internal let _storage: LockStorage<Value>
|
||||
|
||||
/// Initialize the `Value`.
|
||||
@inlinable
|
||||
init(_ value: Value) {
|
||||
public init(_ value: Value) {
|
||||
self._storage = .create(value: value)
|
||||
}
|
||||
|
||||
/// Access the `Value`, allowing mutation of it.
|
||||
@inlinable
|
||||
func withLockedValue<T>(_ mutate: (inout Value) throws -> T) rethrows -> T {
|
||||
return try self._storage.withLockedValue(mutate)
|
||||
public func withLockedValue<T>(_ mutate: (inout Value) throws -> T) rethrows -> T {
|
||||
try self._storage.withLockedValue(mutate)
|
||||
}
|
||||
|
||||
/// Provides an unsafe view over the lock and its value.
|
||||
///
|
||||
/// This can be beneficial when you require fine grained control over the lock in some
|
||||
/// situations but don't want lose the benefits of ``withLockedValue(_:)`` in others by
|
||||
/// switching to ``NIOLock``.
|
||||
public var unsafe: Unsafe {
|
||||
Unsafe(_storage: self._storage)
|
||||
}
|
||||
|
||||
/// Provides an unsafe view over the lock and its value.
|
||||
public struct Unsafe {
|
||||
@usableFromInline
|
||||
let _storage: LockStorage<Value>
|
||||
|
||||
/// Manually acquire the lock.
|
||||
@inlinable
|
||||
public func lock() {
|
||||
self._storage.lock()
|
||||
}
|
||||
|
||||
/// Manually release the lock.
|
||||
@inlinable
|
||||
public func unlock() {
|
||||
self._storage.unlock()
|
||||
}
|
||||
|
||||
/// Mutate the value, assuming the lock has been acquired manually.
|
||||
///
|
||||
/// - Parameter mutate: A closure with scoped access to the value.
|
||||
/// - Returns: The result of the `mutate` closure.
|
||||
@inlinable
|
||||
public func withValueAssumingLockIsAcquired<Result>(
|
||||
_ mutate: (_ value: inout Value) throws -> Result
|
||||
) rethrows -> Result {
|
||||
try self._storage.withUnsafeMutablePointerToHeader { value in
|
||||
try mutate(&value.pointee)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NIOLockedValueBox: Sendable where Value: Sendable {}
|
||||
extension NIOLockedValueBox: @unchecked Sendable where Value: Sendable {}
|
||||
|
||||
extension NIOLockedValueBox.Unsafe: @unchecked Sendable where Value: Sendable {}
|
||||
|
||||
Reference in New Issue
Block a user