Skip to content
Closed
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
6 changes: 6 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,9 @@ jobs:
release-builds:
name: Release builds
uses: apple/swift-nio/.github/workflows/release_builds.yml@main

wasm-sdk:
name: WebAssembly Swift SDK
uses: apple/swift-nio/.github/workflows/wasm_swift_sdk.yml@main
with:
additional_command_arguments: "--target Metrics"
33 changes: 12 additions & 21 deletions Sources/CoreMetrics/Locks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@
//
//===----------------------------------------------------------------------===//

#if canImport(WASILibc)
// No locking on WASILibc
#elseif canImport(Darwin)
#if canImport(Darwin)
import Darwin
#elseif os(Windows)
import WinSDK
Expand All @@ -38,6 +36,11 @@ import Glibc
import Android
#elseif canImport(Musl)
import Musl
#elseif canImport(WASILibc)
import WASILibc
#if canImport(wasi_pthread)
import wasi_pthread
#endif
#else
#error("Unsupported runtime")
#endif
Expand Down Expand Up @@ -142,9 +145,7 @@ extension Lock: @unchecked Sendable {}
/// one used by NIO. On Windows, the lock is based on the substantially similar
/// `SRWLOCK` type.
internal final class ReadWriteLock {
#if canImport(WASILibc)
// WASILibc is single threaded, provides no locks
#elseif os(Windows)
#if os(Windows)
fileprivate let rwlock: UnsafeMutablePointer<SRWLOCK> =
UnsafeMutablePointer.allocate(capacity: 1)
fileprivate var shared: Bool = true
Expand All @@ -155,9 +156,7 @@ internal final class ReadWriteLock {

/// Create a new lock.
public init() {
#if canImport(WASILibc)
// WASILibc is single threaded, provides no locks
#elseif os(Windows)
#if os(Windows)
InitializeSRWLock(self.rwlock)
#else
let err = pthread_rwlock_init(self.rwlock, nil)
Expand All @@ -166,9 +165,7 @@ internal final class ReadWriteLock {
}

deinit {
#if canImport(WASILibc)
// WASILibc is single threaded, provides no locks
#elseif os(Windows)
#if os(Windows)
// SRWLOCK does not need to be free'd
#else
let err = pthread_rwlock_destroy(self.rwlock)
Expand All @@ -182,9 +179,7 @@ internal final class ReadWriteLock {
/// Whenever possible, consider using `withReaderLock` instead of this
/// method and `unlock`, to simplify lock handling.
public func lockRead() {
#if canImport(WASILibc)
// WASILibc is single threaded, provides no locks
#elseif os(Windows)
#if os(Windows)
AcquireSRWLockShared(self.rwlock)
self.shared = true
#else
Expand All @@ -198,9 +193,7 @@ internal final class ReadWriteLock {
/// Whenever possible, consider using `withWriterLock` instead of this
/// method and `unlock`, to simplify lock handling.
public func lockWrite() {
#if canImport(WASILibc)
// WASILibc is single threaded, provides no locks
#elseif os(Windows)
#if os(Windows)
AcquireSRWLockExclusive(self.rwlock)
self.shared = false
#else
Expand All @@ -215,9 +208,7 @@ internal final class ReadWriteLock {
/// instead of this method and `lockRead` and `lockWrite`, to simplify lock
/// handling.
public func unlock() {
#if canImport(WASILibc)
// WASILibc is single threaded, provides no locks
#elseif os(Windows)
#if os(Windows)
if self.shared {
ReleaseSRWLockShared(self.rwlock)
} else {
Expand Down
102 changes: 102 additions & 0 deletions Sources/Metrics/DispatchAsyncWasm/DispatchTime.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Metrics API open source project
//
// Copyright (c) 2018-2019 Apple Inc. and the Swift Metrics API project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift Metrics API project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

/// # About DispatchAsync
///
/// DispatchAsync is a temporary experimental repository aimed at implementing missing Dispatch support in the Swift for WebAssembly SDK.
/// Currently, [Swift for WebAsssembly doesn't include Dispatch](https://book.swiftwasm.org/getting-started/porting.html#swift-foundation-and-dispatch)
/// But, Swift for WebAssembly does support Swift Concurrency. DispatchAsync implements a number of common Dispatch API's using Swift Concurrency
/// under the hood.
///
/// The code in this folder is copy-paste-adapted from [swift-dispatch-async](https://github.com/PassiveLogic/swift-dispatch-async)
///
/// Notes
/// - Copying here avoids adding a temporary new dependency on a repo that will eventually move into the Swift for WebAssembly SDK itself.
/// - This is a temporary measure to enable wasm compilation until swift-dispatch-async is adopted into the Swift for WebAssembly SDK.

#if os(WASI) && !canImport(Dispatch)

/// Drop-in replacement for ``Dispatch.DispatchTime``, implemented using pure Swift.
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
public struct DispatchTime {
private let instant: ContinuousClock.Instant

/// The very first time someone intializes a DispatchTime instance, we
/// reference this static let, causing it to be initialized.
///
/// This is the closest we can get to snapshotting the start time of the running
/// executable, without using OS-specific calls. We want
/// to avoid OS-specific calls to maximize portability.
///
/// To keep this robust, we initialize `self.durationSinceBeginning`
/// to this value using a default value, which is guaranteed to run before any
/// initializers run. This guarantees that uptimeBeginning will be the very
/// first
@available(macOS 13, *)
private static let uptimeBeginning: ContinuousClock.Instant = ContinuousClock.Instant.now

/// See documentation for ``uptimeBeginning``. We intentionally
/// use this to guarantee a capture of `now` to `uptimeBeginning` BEFORE
/// any DispatchTime instances are initialized.
private let durationSinceUptime = uptimeBeginning.duration(to: ContinuousClock.Instant.now)

public init() {
self.instant = ContinuousClock.Instant.now
}

public static func now() -> Self {
DispatchTime()
}

public var uptimeNanoseconds: UInt64 {
let beginning = DispatchTime.uptimeBeginning
let uptimeDuration: Int64 = beginning.duration(to: self.instant).nanosecondsClamped
guard uptimeDuration >= 0 else {
assertionFailure("It shouldn't be possible to get a negative duration since uptimeBeginning.")
return 0
}
return UInt64(uptimeDuration)
}
}

// NOTE: The following was copied from swift-nio/Source/NIOCore/TimeAmount+Duration on June 27, 2025.
//
// See https://github.com/apple/swift-nio/blob/83bc5b58440373a7678b56fa0d9cc22ca55297ee/Sources/NIOCore/TimeAmount%2BDuration.swift
//
// It was copied rather than brought via dependencies to avoid introducing
// a dependency on swift-nio for such a small piece of code.
//
// This library will need to have no depedendencies to be able to be integrated into GCD.
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
extension Swift.Duration {
/// The duration represented as nanoseconds, clamped to maximum expressible value.
fileprivate var nanosecondsClamped: Int64 {
let components = self.components

let secondsComponentNanos = components.seconds.multipliedReportingOverflow(by: 1_000_000_000)
let attosCompononentNanos = components.attoseconds / 1_000_000_000
let combinedNanos = secondsComponentNanos.partialValue.addingReportingOverflow(attosCompononentNanos)

guard
!secondsComponentNanos.overflow,
!combinedNanos.overflow
else {
return .max
}

return combinedNanos.partialValue
}
}

#endif // #if os(WASI) && !canImport(Dispatch)
92 changes: 92 additions & 0 deletions Sources/Metrics/DispatchAsyncWasm/DispatchTimeInterval.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Metrics API open source project
//
// Copyright (c) 2018-2019 Apple Inc. and the Swift Metrics API project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift Metrics API project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

/// # About DispatchAsync
///
/// DispatchAsync is a temporary experimental repository aimed at implementing missing Dispatch support in the Swift for WebAssembly SDK.
/// Currently, [Swift for WebAssembly doesn't include Dispatch](https://book.swiftwasm.org/getting-started/porting.html#swift-foundation-and-dispatch)
/// But, Swift for WebAssembly does support Swift Concurrency. DispatchAsync implements a number of common Dispatch API's using Swift Concurrency
/// under the hood.
///
/// The code in this folder is copy-paste-adapted from [swift-dispatch-async](https://github.com/PassiveLogic/swift-dispatch-async)
///
/// Notes
/// - Copying here avoids adding a temporary new dependency on a repo that will eventually move into the Swift for WebAssembly SDK itself.
/// - This is a temporary measure to enable wasm compilation until swift-dispatch-async is adopted into the Swift for WebAssembly SDK.
/// - The code is completely elided except for wasm compilation targets.
/// - Only the minimum code needed for compilation is copied.

#if os(WASI) && !canImport(Dispatch)

private let kNanosecondsPerSecond: UInt64 = 1_000_000_000
private let kNanosecondsPerMillisecond: UInt64 = 1_000_000
private let kNanoSecondsPerMicrosecond: UInt64 = 1_000

/// NOTE: This is an excerpt from libDispatch, see
/// https://github.com/swiftlang/swift-corelibs-libdispatch/blob/main/src/swift/Time.swift#L168
///
/// Represents a time interval that can be used as an offset from a `DispatchTime`
/// or `DispatchWallTime`.
///
/// For example:
/// let inOneSecond = DispatchTime.now() + DispatchTimeInterval.seconds(1)
///
/// If the requested time interval is larger then the internal representation
/// permits, the result of adding it to a `DispatchTime` or `DispatchWallTime`
/// is `DispatchTime.distantFuture` and `DispatchWallTime.distantFuture`
/// respectively. Such time intervals compare as equal:
///
/// let t1 = DispatchTimeInterval.seconds(Int.max)
/// let t2 = DispatchTimeInterval.milliseconds(Int.max)
/// let result = t1 == t2 // true
public enum DispatchTimeInterval: Equatable, Sendable {
case seconds(Int)
case milliseconds(Int)
case microseconds(Int)
case nanoseconds(Int)
case never

internal var rawValue: Int64 {
switch self {
case .seconds(let s): return clampedInt64Product(Int64(s), Int64(kNanosecondsPerSecond))
case .milliseconds(let ms): return clampedInt64Product(Int64(ms), Int64(kNanosecondsPerMillisecond))
case .microseconds(let us): return clampedInt64Product(Int64(us), Int64(kNanoSecondsPerMicrosecond))
case .nanoseconds(let ns): return Int64(ns)
case .never: return Int64.max
}
}

public static func == (lhs: DispatchTimeInterval, rhs: DispatchTimeInterval) -> Bool {
switch (lhs, rhs) {
case (.never, .never): return true
case (.never, _): return false
case (_, .never): return false
default: return lhs.rawValue == rhs.rawValue
}
}

// Returns m1 * m2, clamped to the range [Int64.min, Int64.max].
// Because of the way this function is used, we can always assume
// that m2 > 0.
private func clampedInt64Product(_ m1: Int64, _ m2: Int64) -> Int64 {
assert(m2 > 0, "multiplier must be positive")
let (result, overflow) = m1.multipliedReportingOverflow(by: m2)
if overflow {
return m1 > 0 ? Int64.max : Int64.min
}
return result
}
}

#endif // #if os(WASI) && !canImport(Dispatch)
6 changes: 5 additions & 1 deletion Sources/Metrics/Metrics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@
// https://github.com/swiftlang/swift/issues/79285

@_exported import CoreMetrics
import Foundation
import typealias Foundation.TimeInterval

@_exported import class CoreMetrics.Timer

#if canImport(Dispatch)
import Dispatch
#endif

extension Timer {
/// Convenience for measuring duration of a closure.
///
Expand Down
Loading