diff --git a/Sources/CoreMetrics/Locks.swift b/Sources/CoreMetrics/Locks.swift index 93fb223..35f282c 100644 --- a/Sources/CoreMetrics/Locks.swift +++ b/Sources/CoreMetrics/Locks.swift @@ -131,6 +131,8 @@ extension Lock { } } +extension Lock: @unchecked Sendable {} + /// A reader/writer threading lock based on `libpthread` instead of `libdispatch`. /// /// This object provides a lock on top of a single `pthread_rwlock_t`. This kind @@ -273,3 +275,5 @@ extension ReadWriteLock { try self.withWriterLock(body) } } + +extension ReadWriteLock: @unchecked Sendable {} diff --git a/Sources/CoreMetrics/Metrics.swift b/Sources/CoreMetrics/Metrics.swift index 139130e..a37cc33 100644 --- a/Sources/CoreMetrics/Metrics.swift +++ b/Sources/CoreMetrics/Metrics.swift @@ -600,7 +600,8 @@ public enum MetricsSystem { return try self._factory.withWriterLock(body) } - private final class FactoryBox { + // This can be `@unchecked Sendable` because we're manually gating access to mutable state with a lock. + private final class FactoryBox: @unchecked Sendable { private let lock = ReadWriteLock() fileprivate var _underlying: MetricsFactory private var initialized = false @@ -797,9 +798,10 @@ internal final class AccumulatingRoundingFloatingPointCounter: FloatingPointCoun } /// Wraps a RecorderHandler, adding support for incrementing values by storing an accumulated value and recording increments to the underlying CounterHandler after crossing integer boundaries. -internal final class AccumulatingMeter: MeterHandler { +/// - Note: we can annotate this class as `@unchecked Sendable` because we are manually gating access to mutable state (i.e., the `value` property) via a Lock. +internal final class AccumulatingMeter: MeterHandler, @unchecked Sendable { private let recorderHandler: RecorderHandler - // FIXME: use atomics when available + // FIXME: use swift-atomics when floating point support is available private var value: Double = 0 private let lock = Lock() diff --git a/Sources/Metrics/Metrics.swift b/Sources/Metrics/Metrics.swift index d99c4c6..58e8ff3 100644 --- a/Sources/Metrics/Metrics.swift +++ b/Sources/Metrics/Metrics.swift @@ -60,6 +60,16 @@ extension Timer { /// - duration: The duration to record. @inlinable public func record(_ duration: DispatchTimeInterval) { + // This wrapping in a optional is a workaround because DispatchTimeInterval + // is a non-frozen public enum and Dispatch is built with library evolution + // mode turned on. + // This means we should have an `@unknown default` case, but this breaks + // on non-Darwin platforms. + // Switching over an optional means that the `.none` case will map to + // `default` (which means we'll always have a valid case to go into + // the default case), but in reality this case will never exist as this + // optional will never be nil. + let duration = Optional(duration) switch duration { case .nanoseconds(let value): self.recordNanoseconds(value) @@ -71,6 +81,8 @@ extension Timer { self.recordSeconds(value) case .never: self.record(0) + default: + self.record(0) } } } diff --git a/Tests/MetricsTests/MetricsTests.swift b/Tests/MetricsTests/MetricsTests.swift index 49b5f7b..9a4541f 100644 --- a/Tests/MetricsTests/MetricsTests.swift +++ b/Tests/MetricsTests/MetricsTests.swift @@ -185,7 +185,17 @@ class MetricsExtensionsTests: XCTestCase { // https://bugs.swift.org/browse/SR-6310 extension DispatchTimeInterval { func nano() -> Int { - switch self { + // This wrapping in a optional is a workaround because DispatchTimeInterval + // is a non-frozen public enum and Dispatch is built with library evolution + // mode turned on. + // This means we should have an `@unknown default` case, but this breaks + // on non-Darwin platforms. + // Switching over an optional means that the `.none` case will map to + // `default` (which means we'll always have a valid case to go into + // the default case), but in reality this case will never exist as this + // optional will never be nil. + let interval = Optional(self) + switch interval { case .nanoseconds(let value): return value case .microseconds(let value): @@ -196,6 +206,8 @@ extension DispatchTimeInterval { return value * 1_000_000_000 case .never: return 0 + default: + return 0 } } }