Skip to content

[Observation] Use independent locking since the runtime functions reference a symbol that is not emitted #81185

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
list(APPEND swift_runtime_library_compile_flags -I${SWIFT_SOURCE_DIR}/stdlib/include -I${SWIFT_SOURCE_DIR}/include)

add_swift_target_library(swiftObservation ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS_STDLIB
Locking.cpp
Locking.swift
Observable.swift
ObservationRegistrar.swift
Expand Down
42 changes: 0 additions & 42 deletions stdlib/public/Observation/Sources/Observation/Locking.cpp

This file was deleted.

171 changes: 142 additions & 29 deletions stdlib/public/Observation/Sources/Observation/Locking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,52 +9,165 @@
//
//===----------------------------------------------------------------------===//

// NOTE: this cant use Synchronization because it is deployed before that was
// introduced and availability wont let it be hidden behind an internal type.
// The Swift internal runtime locking cannot be used since that emits dependent
// symbols that are not provided by this library - so instead it has to re-implement
// all of this on its own...

@_silgen_name("_swift_observation_lock_size")
func _lockSize() -> Int
#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#elseif canImport(Musl)
import Musl
#elseif canImport(WinSDK)
import WinSDK
#elseif canImport(Bionic)
import Bionic
#elseif arch(wasm32)
#else
#error("Unsupported platform")
#endif

@_silgen_name("_swift_observation_lock_init")
func _lockInit(_: UnsafeRawPointer)
internal struct Lock {
#if canImport(Darwin)
typealias Primitive = os_unfair_lock
#elseif canImport(Glibc) || canImport(Musl) || canImport(Bionic)
typealias Primitive = pthread_mutex_t
#elseif canImport(WinSDK)
typealias Primitive = SRWLOCK
#elseif arch(wasm32)
typealias Primitive = Int
#else
#error("Unsupported platform")
#endif

@_silgen_name("_swift_observation_lock_lock")
func _lockLock(_: UnsafeRawPointer)
typealias PlatformLock = UnsafeMutablePointer<Primitive>
let platformLock: PlatformLock

@_silgen_name("_swift_observation_lock_unlock")
func _lockUnlock(_: UnsafeRawPointer)
private init(_ platformLock: PlatformLock) {
self.platformLock = platformLock
}

@available(SwiftStdlib 5.9, *)
internal struct _ManagedCriticalState<State> {
final private class LockedBuffer: ManagedBuffer<State, UnsafeRawPointer> { }
fileprivate static func initialize(_ platformLock: PlatformLock) {
#if canImport(Darwin)
platformLock.initialize(to: os_unfair_lock())
#elseif canImport(Glibc) || canImport(Musl) || canImport(Bionic)
let result = pthread_mutex_init(platformLock, nil)
precondition(result == 0, "pthread_mutex_init failed")
#elseif canImport(WinSDK)
InitializeSRWLock(platformLock)
#elseif arch(wasm32)
platformLock.initialize(to: 0)
#else
#error("Unsupported platform")
#endif
}

fileprivate static func deinitialize(_ platformLock: PlatformLock) {
#if canImport(Glibc) || canImport(Musl) || canImport(Bionic)
let result = pthread_mutex_destroy(platformLock)
precondition(result == 0, "pthread_mutex_destroy failed")
#endif
platformLock.deinitialize(count: 1)
}

fileprivate static func lock(_ platformLock: PlatformLock) {
#if canImport(Darwin)
os_unfair_lock_lock(platformLock)
#elseif canImport(Glibc) || canImport(Musl) || canImport(Bionic)
pthread_mutex_lock(platformLock)
#elseif canImport(WinSDK)
AcquireSRWLockExclusive(platformLock)
#elseif arch(wasm32)
#else
#error("Unsupported platform")
#endif
}

fileprivate static func unlock(_ platformLock: PlatformLock) {
#if canImport(Darwin)
os_unfair_lock_unlock(platformLock)
#elseif canImport(Glibc) || canImport(Musl) || canImport(Bionic)
let result = pthread_mutex_unlock(platformLock)
precondition(result == 0, "pthread_mutex_unlock failed")
#elseif canImport(WinSDK)
ReleaseSRWLockExclusive(platformLock)
#elseif arch(wasm32)
#else
#error("Unsupported platform")
#endif
}

static func allocate() -> Lock {
let platformLock = PlatformLock.allocate(capacity: 1)
initialize(platformLock)
return Lock(platformLock)
}

func deinitialize() {
Lock.deinitialize(platformLock)
platformLock.deallocate()
}

func lock() {
Lock.lock(platformLock)
}

func unlock() {
Lock.unlock(platformLock)
}

private let buffer: ManagedBuffer<State, UnsafeRawPointer>
/// Acquire the 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.
///
/// - Parameter body: The block to execute while holding the lock.
/// - Returns: The value returned by the block.
func withLock<T>(_ body: () throws -> T) rethrows -> T {
self.lock()
defer {
self.unlock()
}
return try body()
}

// specialise Void return (for performance)
func withLockVoid(_ body: () throws -> Void) rethrows {
try self.withLock(body)
}
}

internal init(_ buffer: ManagedBuffer<State, UnsafeRawPointer>) {
self.buffer = buffer
struct _ManagedCriticalState<State> {
private final class LockedBuffer: ManagedBuffer<State, Lock.Primitive> {
deinit {
withUnsafeMutablePointerToElements { Lock.deinitialize($0) }
}
}

internal init(_ initial: State) {
let roundedSize = (_lockSize() + MemoryLayout<UnsafeRawPointer>.size - 1) / MemoryLayout<UnsafeRawPointer>.size
self.init(LockedBuffer.create(minimumCapacity: Swift.max(roundedSize, 1)) { buffer in
buffer.withUnsafeMutablePointerToElements { _lockInit(UnsafeRawPointer($0)) }

private let buffer: ManagedBuffer<State, Lock.Primitive>

init(_ initial: State) {
buffer = LockedBuffer.create(minimumCapacity: 1) { buffer in
buffer.withUnsafeMutablePointerToElements { Lock.initialize($0) }
return initial
})
}
}

internal func withCriticalRegion<R>(
_ critical: (inout State) throws -> R
) rethrows -> R {
func withCriticalRegion<R>(_ critical: (inout State) throws -> R) rethrows -> R {
try buffer.withUnsafeMutablePointers { header, lock in
_lockLock(UnsafeRawPointer(lock))
defer {
_lockUnlock(UnsafeRawPointer(lock))
}
Lock.lock(lock)
defer { Lock.unlock(lock) }
return try critical(&header.pointee)
}
}
}

@available(SwiftStdlib 5.9, *)
extension _ManagedCriticalState: @unchecked Sendable where State: Sendable { }
extension _ManagedCriticalState: @unchecked Sendable where State: Sendable {}


@available(SwiftStdlib 5.9, *)
extension _ManagedCriticalState: Identifiable {
Expand Down