Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
}
78 changes: 40 additions & 38 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,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: ())
}

Expand All @@ -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()
}

Expand All @@ -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)
}
}

Expand All @@ -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()
Expand All @@ -252,31 +269,16 @@ 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
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
}()
)
}
57 changes: 49 additions & 8 deletions Sources/Prometheus/NIOLockedValueBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Loading