Skip to content

Commit ca24acd

Browse files
committed
[Concurrency] Make DispatchMainExecutor safer.
Don't shoehorn pointers into the `ExecutorEvent`; instead, keep a table. This does mean using a `Mutex`, sadly, but that's the price we pay for being safe. Also, this lets us destroy the Dispatch sources properly. rdar://141348916
1 parent 47efda5 commit ca24acd

File tree

1 file changed

+36
-6
lines changed

1 file changed

+36
-6
lines changed

stdlib/public/Concurrency/DispatchExecutor.swift

+36-6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#if !$Embedded
1414

1515
import Swift
16+
import Synchronization
1617

1718
// We can't import Dispatch from here, sadly, because it apparently has a
1819
// transitive dependency on Combine (which in turn depends on _Concurrency).
@@ -24,11 +25,28 @@ import Swift
2425
// .. Main Executor ............................................................
2526

2627
@available(SwiftStdlib 6.2, *)
28+
@safe
2729
public class DispatchMainExecutor: RunLoopExecutor, @unchecked Sendable {
2830
var threaded = false
2931

32+
@unsafe
33+
struct EventTable {
34+
var nextEvent: Int = 0
35+
var events: [Int: OpaquePointer] = unsafe [:]
36+
}
37+
38+
let eventTable = unsafe Mutex<EventTable>(EventTable())
39+
3040
public init() {}
3141

42+
deinit {
43+
eventTable.withLock { table in
44+
for (ident, source) in table.events {
45+
_destroyDispatchEvent(source)
46+
}
47+
}
48+
}
49+
3250
public func run() throws {
3351
if threaded {
3452
fatalError("DispatchMainExecutor does not support recursion")
@@ -88,23 +106,35 @@ extension DispatchMainExecutor: EventableExecutor {
88106
handler: @escaping @Sendable () -> ()
89107
) -> ExecutorEvent {
90108
let source = unsafe _createDispatchEvent(handler: handler)
109+
let eventId = unsafe eventTable.withLock { table in
110+
let eventId = table.nextEvent
111+
table.nextEvent += 1
112+
table.events[eventId] = unsafe source
113+
return eventId
114+
}
91115

92-
// Stash the pointer in the id of the ExecutorEvent struct
93-
let eventId = unsafe unsafeBitCast(source, to: Int.self)
94116
return ExecutorEvent(id: eventId)
95117
}
96118

97119
/// Deregister the given event.
98120
public func deregister(event: ExecutorEvent) {
99-
// Extract the source and cancel it
100-
let source = unsafe unsafeBitCast(event.id, to: OpaquePointer.self)
121+
guard let source = unsafe eventTable.withLock(
122+
{ unsafe $0.events.removeValue(forKey: event.id) }
123+
) else {
124+
return
125+
}
126+
101127
unsafe _destroyDispatchEvent(source)
102128
}
103129

104130
/// Notify the executor of an event.
105131
public func notify(event: ExecutorEvent) {
106-
// Extract the source, but don't release it
107-
let source = unsafe unsafeBitCast(event.id, to: OpaquePointer.self)
132+
guard let source = unsafe eventTable.withLock(
133+
{ unsafe $0.events[event.id] }
134+
) else {
135+
return
136+
}
137+
108138
unsafe _signalDispatchEvent(source)
109139
}
110140

0 commit comments

Comments
 (0)