Skip to content

Commit 3f157ab

Browse files
authored
[Observation] Use independent locking since the runtime functions reference a symbol that is not emitted (#81185)
The runtime functions for locking end up referencing a symbol that is not publicly emitted. This leads to broken linux builds. Since that interface is not public and does not offer a stable mechanism and Synchronization cannot be used (due to it being a higher deployment target) instead it has to revert to implementing locking from scratch for all platforms. This is mostly replicated from the swift-async-algorithms package and should be usable enough to accomplish the goals of this module. Resolves rdar://150060874
1 parent 6feba10 commit 3f157ab

File tree

3 files changed

+142
-72
lines changed

3 files changed

+142
-72
lines changed

stdlib/public/Observation/Sources/Observation/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
list(APPEND swift_runtime_library_compile_flags -I${SWIFT_SOURCE_DIR}/stdlib/include -I${SWIFT_SOURCE_DIR}/include)
1414

1515
add_swift_target_library(swiftObservation ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS_STDLIB
16-
Locking.cpp
1716
Locking.swift
1817
Observable.swift
1918
ObservationRegistrar.swift

stdlib/public/Observation/Sources/Observation/Locking.cpp

Lines changed: 0 additions & 42 deletions
This file was deleted.

stdlib/public/Observation/Sources/Observation/Locking.swift

Lines changed: 142 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,52 +9,165 @@
99
//
1010
//===----------------------------------------------------------------------===//
1111

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

13-
@_silgen_name("_swift_observation_lock_size")
14-
func _lockSize() -> Int
18+
#if canImport(Darwin)
19+
import Darwin
20+
#elseif canImport(Glibc)
21+
import Glibc
22+
#elseif canImport(Musl)
23+
import Musl
24+
#elseif canImport(WinSDK)
25+
import WinSDK
26+
#elseif canImport(Bionic)
27+
import Bionic
28+
#elseif arch(wasm32)
29+
#else
30+
#error("Unsupported platform")
31+
#endif
1532

16-
@_silgen_name("_swift_observation_lock_init")
17-
func _lockInit(_: UnsafeRawPointer)
33+
internal struct Lock {
34+
#if canImport(Darwin)
35+
typealias Primitive = os_unfair_lock
36+
#elseif canImport(Glibc) || canImport(Musl) || canImport(Bionic)
37+
typealias Primitive = pthread_mutex_t
38+
#elseif canImport(WinSDK)
39+
typealias Primitive = SRWLOCK
40+
#elseif arch(wasm32)
41+
typealias Primitive = Int
42+
#else
43+
#error("Unsupported platform")
44+
#endif
1845

19-
@_silgen_name("_swift_observation_lock_lock")
20-
func _lockLock(_: UnsafeRawPointer)
46+
typealias PlatformLock = UnsafeMutablePointer<Primitive>
47+
let platformLock: PlatformLock
2148

22-
@_silgen_name("_swift_observation_lock_unlock")
23-
func _lockUnlock(_: UnsafeRawPointer)
49+
private init(_ platformLock: PlatformLock) {
50+
self.platformLock = platformLock
51+
}
2452

25-
@available(SwiftStdlib 5.9, *)
26-
internal struct _ManagedCriticalState<State> {
27-
final private class LockedBuffer: ManagedBuffer<State, UnsafeRawPointer> { }
53+
fileprivate static func initialize(_ platformLock: PlatformLock) {
54+
#if canImport(Darwin)
55+
platformLock.initialize(to: os_unfair_lock())
56+
#elseif canImport(Glibc) || canImport(Musl) || canImport(Bionic)
57+
let result = pthread_mutex_init(platformLock, nil)
58+
precondition(result == 0, "pthread_mutex_init failed")
59+
#elseif canImport(WinSDK)
60+
InitializeSRWLock(platformLock)
61+
#elseif arch(wasm32)
62+
platformLock.initialize(to: 0)
63+
#else
64+
#error("Unsupported platform")
65+
#endif
66+
}
67+
68+
fileprivate static func deinitialize(_ platformLock: PlatformLock) {
69+
#if canImport(Glibc) || canImport(Musl) || canImport(Bionic)
70+
let result = pthread_mutex_destroy(platformLock)
71+
precondition(result == 0, "pthread_mutex_destroy failed")
72+
#endif
73+
platformLock.deinitialize(count: 1)
74+
}
75+
76+
fileprivate static func lock(_ platformLock: PlatformLock) {
77+
#if canImport(Darwin)
78+
os_unfair_lock_lock(platformLock)
79+
#elseif canImport(Glibc) || canImport(Musl) || canImport(Bionic)
80+
pthread_mutex_lock(platformLock)
81+
#elseif canImport(WinSDK)
82+
AcquireSRWLockExclusive(platformLock)
83+
#elseif arch(wasm32)
84+
#else
85+
#error("Unsupported platform")
86+
#endif
87+
}
88+
89+
fileprivate static func unlock(_ platformLock: PlatformLock) {
90+
#if canImport(Darwin)
91+
os_unfair_lock_unlock(platformLock)
92+
#elseif canImport(Glibc) || canImport(Musl) || canImport(Bionic)
93+
let result = pthread_mutex_unlock(platformLock)
94+
precondition(result == 0, "pthread_mutex_unlock failed")
95+
#elseif canImport(WinSDK)
96+
ReleaseSRWLockExclusive(platformLock)
97+
#elseif arch(wasm32)
98+
#else
99+
#error("Unsupported platform")
100+
#endif
101+
}
102+
103+
static func allocate() -> Lock {
104+
let platformLock = PlatformLock.allocate(capacity: 1)
105+
initialize(platformLock)
106+
return Lock(platformLock)
107+
}
108+
109+
func deinitialize() {
110+
Lock.deinitialize(platformLock)
111+
platformLock.deallocate()
112+
}
113+
114+
func lock() {
115+
Lock.lock(platformLock)
116+
}
117+
118+
func unlock() {
119+
Lock.unlock(platformLock)
120+
}
28121

29-
private let buffer: ManagedBuffer<State, UnsafeRawPointer>
122+
/// Acquire the lock for the duration of the given block.
123+
///
124+
/// This convenience method should be preferred to `lock` and `unlock` in
125+
/// most situations, as it ensures that the lock will be released regardless
126+
/// of how `body` exits.
127+
///
128+
/// - Parameter body: The block to execute while holding the lock.
129+
/// - Returns: The value returned by the block.
130+
func withLock<T>(_ body: () throws -> T) rethrows -> T {
131+
self.lock()
132+
defer {
133+
self.unlock()
134+
}
135+
return try body()
136+
}
137+
138+
// specialise Void return (for performance)
139+
func withLockVoid(_ body: () throws -> Void) rethrows {
140+
try self.withLock(body)
141+
}
142+
}
30143

31-
internal init(_ buffer: ManagedBuffer<State, UnsafeRawPointer>) {
32-
self.buffer = buffer
144+
struct _ManagedCriticalState<State> {
145+
private final class LockedBuffer: ManagedBuffer<State, Lock.Primitive> {
146+
deinit {
147+
withUnsafeMutablePointerToElements { Lock.deinitialize($0) }
148+
}
33149
}
34-
35-
internal init(_ initial: State) {
36-
let roundedSize = (_lockSize() + MemoryLayout<UnsafeRawPointer>.size - 1) / MemoryLayout<UnsafeRawPointer>.size
37-
self.init(LockedBuffer.create(minimumCapacity: Swift.max(roundedSize, 1)) { buffer in
38-
buffer.withUnsafeMutablePointerToElements { _lockInit(UnsafeRawPointer($0)) }
150+
151+
private let buffer: ManagedBuffer<State, Lock.Primitive>
152+
153+
init(_ initial: State) {
154+
buffer = LockedBuffer.create(minimumCapacity: 1) { buffer in
155+
buffer.withUnsafeMutablePointerToElements { Lock.initialize($0) }
39156
return initial
40-
})
157+
}
41158
}
42159

43-
internal func withCriticalRegion<R>(
44-
_ critical: (inout State) throws -> R
45-
) rethrows -> R {
160+
func withCriticalRegion<R>(_ critical: (inout State) throws -> R) rethrows -> R {
46161
try buffer.withUnsafeMutablePointers { header, lock in
47-
_lockLock(UnsafeRawPointer(lock))
48-
defer {
49-
_lockUnlock(UnsafeRawPointer(lock))
50-
}
162+
Lock.lock(lock)
163+
defer { Lock.unlock(lock) }
51164
return try critical(&header.pointee)
52165
}
53166
}
54167
}
55168

56-
@available(SwiftStdlib 5.9, *)
57-
extension _ManagedCriticalState: @unchecked Sendable where State: Sendable { }
169+
extension _ManagedCriticalState: @unchecked Sendable where State: Sendable {}
170+
58171

59172
@available(SwiftStdlib 5.9, *)
60173
extension _ManagedCriticalState: Identifiable {

0 commit comments

Comments
 (0)