Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .licenseignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*.json
Package.swift
**/Package.swift
Package@-*.swift
Package@swift*.swift
**/Package@-*.swift
Package.resolved
**/Package.resolved
Expand Down
52 changes: 52 additions & 0 deletions [email protected]
Original file line number Diff line number Diff line change
@@ -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
}
66 changes: 34 additions & 32 deletions Sources/Prometheus/NIOLock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -47,7 +54,7 @@ typealias LockPrimitive = pthread_mutex_t
#endif

@usableFromInline
enum LockOperations {}
enum LockOperations: Sendable {}

extension LockOperations {
@inlinable
Expand All @@ -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)")
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}

Expand All @@ -189,11 +201,16 @@ 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
Expand Down Expand Up @@ -229,7 +246,7 @@ struct NIOLock {

@inlinable
internal func withLockPrimitive<T>(_ body: (UnsafeMutablePointer<LockPrimitive>) throws -> T) rethrows -> T {
return try self._storage.withLockPrimitive(body)
try self._storage.withLockPrimitive(body)
}
}

Expand Down Expand Up @@ -257,26 +274,11 @@ extension NIOLock {
}
}

extension NIOLock: Sendable {}
extension NIOLock: @unchecked Sendable {}

extension UnsafeMutablePointer {
@inlinable
func assertValidAlignment() {
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
}()
)
}
50 changes: 46 additions & 4 deletions Sources/Prometheus/NIOLockedValueBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@

/// 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.
Expand All @@ -49,8 +49,50 @@ struct NIOLockedValueBox<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)
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``.
var unsafe: Unsafe {
Unsafe(_storage: self._storage)
}

/// Provides an unsafe view over the lock and its value.
struct Unsafe {
@usableFromInline
let _storage: LockStorage<Value>

/// Manually acquire the lock.
@inlinable
func lock() {
self._storage.lock()
}

/// Manually release the lock.
@inlinable
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
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 {}