Skip to content

Commit 6dab9b6

Browse files
committed
Update ThreadUtils and add test case
1 parent f153d37 commit 6dab9b6

File tree

5 files changed

+106
-24
lines changed

5 files changed

+106
-24
lines changed

Sources/OpenSwiftUI/Core/Update/Update.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ enum Update {
7777

7878
let actions = Update.actions
7979
Update.actions = []
80-
performOnMainThread {
80+
onMainThread {
8181
// TODO: Signpost.postUpdateActions
8282
begin()
8383
for action in actions {

Sources/OpenSwiftUI/Core/Util/ThreadUtils.swift

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

Sources/OpenSwiftUI/Data/Model/State/StoredLocation.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ class StoredLocationBase<Value>: AnyLocation<Value>, Location {
107107
}
108108
var newTransaction = transaction
109109
newTransaction.override(.current)
110-
performOnMainThread { [weak self] in
110+
onMainThread { [weak self] in
111111
guard let self else {
112112
return
113113
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//
2+
// ThreadUtils.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for RELEASE_2024
6+
// Status: Complete
7+
8+
import Foundation
9+
10+
final package class ThreadSpecific<T> {
11+
var key: pthread_key_t
12+
let defaultValue: T
13+
14+
package init(_ defaultValue: T) {
15+
key = 0
16+
self.defaultValue = defaultValue
17+
pthread_key_create(&key) { pointer in
18+
pointer.withMemoryRebound(to: Any.self, capacity: 1) { ptr in
19+
ptr.deinitialize(count: 1)
20+
ptr.deallocate()
21+
}
22+
}
23+
}
24+
25+
deinit {
26+
preconditionFailure("\(Self.self).deinit is unsafe and would leak", file: #file, line: #line)
27+
}
28+
29+
private final var box: UnsafeMutablePointer<Any> {
30+
let pointer = pthread_getspecific(key)
31+
if let pointer {
32+
return pointer.assumingMemoryBound(to: Any.self)
33+
} else {
34+
let box = UnsafeMutablePointer<Any>.allocate(capacity: 1)
35+
pthread_setspecific(key, box)
36+
box.initialize(to: defaultValue)
37+
return box
38+
}
39+
}
40+
41+
final package var value: T {
42+
get {
43+
box.pointee as! T
44+
}
45+
set {
46+
box.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee = newValue }
47+
}
48+
}
49+
}
50+
51+
package func onMainThread(do body: @escaping () -> Void) {
52+
#if os(WASI)
53+
// See #76: Thread and RunLoopMode.common is not available on WASI currently
54+
block()
55+
#else
56+
if Thread.isMainThread {
57+
body()
58+
} else {
59+
RunLoop.main.perform(inModes: [.common], block: body)
60+
}
61+
#endif
62+
}
63+
64+
package func mainThreadPrecondition() {
65+
guard Thread.isMainThread else {
66+
fatalError("calling into OpenSwiftUI on a non-main thread is not supported")
67+
}
68+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//
2+
// ThreadUtilsTest.swift
3+
// OpenSwiftUICoreTests
4+
5+
import OpenSwiftUICore
6+
import Testing
7+
8+
struct ThreadUtilsTest {
9+
static let defaultValue: Int = 1
10+
static let box = ThreadSpecific(defaultValue)
11+
12+
@Test
13+
func value() async throws {
14+
let box = ThreadUtilsTest.box
15+
#expect(box.value == ThreadUtilsTest.defaultValue)
16+
try await withThrowingTaskGroup(of: Int.self) { group in
17+
group.addTask {
18+
await Task.detached {
19+
box.value = 3
20+
#expect(box.value == 3)
21+
return box.value
22+
}.value
23+
}
24+
group.addTask {
25+
await Task.detached {
26+
box.value = 4
27+
#expect(box.value == 4)
28+
return box.value
29+
}.value
30+
}
31+
let result = try await group.reduce(0, +)
32+
#expect(result == 7)
33+
#expect(box.value == ThreadUtilsTest.defaultValue)
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)