From e15cf8b5610abc981afa61314420a3aafc26e69f Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 1 Oct 2025 11:54:49 +0100 Subject: [PATCH] Add Windows support by fixing Locks.swift to support windows **Motivation:** Swift is cross platform, including Windows, we should support distributed tracing on any platform **Modifications:** Add windows CI and make necessary changes **Result:** Windows support --- .github/workflows/main.yml | 8 ++ .github/workflows/pull_request.yml | 8 ++ Sources/Instrumentation/Locks.swift | 120 +++++++++++++++++++++------- Sources/Tracing/TracingTime.swift | 15 ++++ 4 files changed, 120 insertions(+), 31 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a46dd2b..7bd9679 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,6 +18,14 @@ jobs: linux_6_2_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" linux_nightly_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + windows_6_0_enabled: true + windows_6_1_enabled: true + windows_nightly_next_enabled: true + windows_nightly_main_enabled: true + windows_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + windows_6_1_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + windows_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + windows_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" benchmarks: name: Benchmarks diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index c229a1a..ec3683a 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -40,6 +40,14 @@ jobs: linux_6_2_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" linux_nightly_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + windows_6_0_enabled: true + windows_6_1_enabled: true + windows_nightly_next_enabled: true + windows_nightly_main_enabled: true + windows_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + windows_6_1_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + windows_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + windows_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" benchmarks: name: Benchmarks diff --git a/Sources/Instrumentation/Locks.swift b/Sources/Instrumentation/Locks.swift index b04ea62..72bc54b 100644 --- a/Sources/Instrumentation/Locks.swift +++ b/Sources/Instrumentation/Locks.swift @@ -26,80 +26,126 @@ // //===----------------------------------------------------------------------===// -#if canImport(Darwin) +#if canImport(WASILibc) +// No locking on WASILibc +#elseif canImport(Darwin) import Darwin +#elseif os(Windows) +import WinSDK #elseif canImport(Glibc) import Glibc #elseif canImport(Android) 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 -/// A threading lock based on `libpthread` instead of `libdispatch`. +/// A reader/writer threading lock based on `libpthread` instead of `libdispatch`. /// -/// This object provides a lock on top of a single `pthread_mutex_t`. This kind +/// This object provides a lock on top of a single `pthread_rwlock_t`. This kind /// of lock is safe to use with `libpthread`-based threading models, such as the -/// one used by NIO. -@_spi(Locking) // Use the `package` access modifier once min Swift version is increased. -public final class ReadWriteLock { - private let rwlock: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: 1) +/// one used by NIO. On Windows, the lock is based on the substantially similar +/// `SRWLOCK` type. +public final class ReadWriteLock: @unchecked Sendable { + #if canImport(WASILibc) + // WASILibc is single threaded, provides no locks + #elseif os(Windows) + fileprivate let rwlock: UnsafeMutablePointer = + UnsafeMutablePointer.allocate(capacity: 1) + fileprivate var shared: Bool = true + #else + fileprivate let rwlock: UnsafeMutablePointer = + UnsafeMutablePointer.allocate(capacity: 1) + #endif /// Create a new lock. public init() { + #if canImport(WASILibc) + // WASILibc is single threaded, provides no locks + #elseif os(Windows) + InitializeSRWLock(self.rwlock) + #else let err = pthread_rwlock_init(self.rwlock, nil) - precondition(err == 0, "pthread_rwlock_init failed with error \(err)") + precondition(err == 0, "\(#function) failed in pthread_rwlock with error \(err)") + #endif } deinit { + #if canImport(WASILibc) + // WASILibc is single threaded, provides no locks + #elseif os(Windows) + // SRWLOCK does not need to be free'd + self.rwlock.deallocate() + #else let err = pthread_rwlock_destroy(self.rwlock) - precondition(err == 0, "pthread_rwlock_destroy failed with error \(err)") + precondition(err == 0, "\(#function) failed in pthread_rwlock with error \(err)") self.rwlock.deallocate() + #endif } /// Acquire a reader lock. /// - /// Whenever possible, consider using `withLock` instead of this method and - /// `unlock`, to simplify lock handling. + /// 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) + AcquireSRWLockShared(self.rwlock) + self.shared = true + #else let err = pthread_rwlock_rdlock(self.rwlock) - precondition(err == 0, "pthread_rwlock_rdlock failed with error \(err)") + precondition(err == 0, "\(#function) failed in pthread_rwlock with error \(err)") + #endif } /// Acquire a writer lock. /// - /// Whenever possible, consider using `withLock` instead of this method and - /// `unlock`, to simplify lock handling. + /// 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) + AcquireSRWLockExclusive(self.rwlock) + self.shared = false + #else let err = pthread_rwlock_wrlock(self.rwlock) - precondition(err == 0, "pthread_rwlock_wrlock failed with error \(err)") + precondition(err == 0, "\(#function) failed in pthread_rwlock with error \(err)") + #endif } /// Release the lock. /// - /// Whenever possible, consider using `withLock` instead of this method and - /// `lock`, to simplify lock handling. + /// Whenever possible, consider using `withReaderLock` and `withWriterLock` + /// 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 self.shared { + ReleaseSRWLockShared(self.rwlock) + } else { + ReleaseSRWLockExclusive(self.rwlock) + } + #else let err = pthread_rwlock_unlock(self.rwlock) - precondition(err == 0, "pthread_rwlock_unlock failed with error \(err)") + precondition(err == 0, "\(#function) failed in pthread_rwlock with error \(err)") + #endif } } extension ReadWriteLock { /// Acquire the reader lock for the duration of the given block. /// - /// This convenience method should be preferred to `lock` and `unlock` in - /// most situations, as it ensures that the lock will be released regardless - /// of how `body` exits. + /// This convenience method should be preferred to `lockRead` and `unlock` + /// in most situations, as it ensures that the lock will be released + /// regardless of how `body` exits. /// - /// - Parameter body: The block to execute while holding the lock. + /// - Parameter body: The block to execute while holding the reader lock. /// - Returns: The value returned by the block. @inlinable public func withReaderLock(_ body: () throws -> T) rethrows -> T { @@ -112,11 +158,11 @@ extension ReadWriteLock { /// Acquire the writer lock for the duration of the given block. /// - /// This convenience method should be preferred to `lock` and `unlock` in - /// most situations, as it ensures that the lock will be released regardless - /// of how `body` exits. + /// This convenience method should be preferred to `lockWrite` and `unlock` + /// in most situations, as it ensures that the lock will be released + /// regardless of how `body` exits. /// - /// - Parameter body: The block to execute while holding the lock. + /// - Parameter body: The block to execute while holding the writer lock. /// - Returns: The value returned by the block. @inlinable public func withWriterLock(_ body: () throws -> T) rethrows -> T { @@ -126,6 +172,18 @@ extension ReadWriteLock { } return try body() } + + // specialise Void return (for performance) + @inlinable + internal func withReaderLockVoid(_ body: () throws -> Void) rethrows { + try self.withReaderLock(body) + } + + // specialise Void return (for performance) + @inlinable + internal func withWriterLockVoid(_ body: () throws -> Void) rethrows { + try self.withWriterLock(body) + } } /// A wrapper providing locked access to a value. diff --git a/Sources/Tracing/TracingTime.swift b/Sources/Tracing/TracingTime.swift index c14c165..ed349af 100644 --- a/Sources/Tracing/TracingTime.swift +++ b/Sources/Tracing/TracingTime.swift @@ -25,6 +25,8 @@ import Android import Musl #elseif canImport(WASILibc) import WASILibc +#elseif os(Windows) +import WinSDK #else #error("Unsupported runtime") #endif @@ -110,6 +112,18 @@ public struct DefaultTracerClock { /// Returns the current instant in time. public var now: Self.Instant { + #if os(Windows) + var fileTime = FILETIME() + GetSystemTimePreciseAsFileTime(&fileTime) + + let fileTime64 = (UInt64(fileTime.dwHighDateTime) << 32) | UInt64(fileTime.dwLowDateTime) + + let windowsToUnixEpochIn100ns: UInt64 = 116_444_736_000_000_000 + let unixTime100ns = fileTime64 &- windowsToUnixEpochIn100ns + let nowNanos = unixTime100ns &* 100 + + return Instant(nanosecondsSinceEpoch: nowNanos) + #else // not Windows var ts = timespec() #if os(WASI) CWASI_clock_gettime_realtime(&ts) @@ -122,5 +136,6 @@ public struct DefaultTracerClock { let nowNanos = UInt64(ts.tv_sec) &* 1_000_000_000 &+ UInt64(ts.tv_nsec) return Instant(nanosecondsSinceEpoch: nowNanos) + #endif // Windows } }