-
-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Assert that
JSObject
is being accessed only from the owner thread
- Loading branch information
1 parent
afada10
commit 45206f7
Showing
4 changed files
with
224 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
#if _runtime(_multithreaded) | ||
#if canImport(wasi_pthread) | ||
import wasi_pthread | ||
#elseif canImport(Darwin) | ||
import Darwin | ||
#elseif canImport(Glibc) | ||
import Glibc | ||
#else | ||
#error("Unsupported platform") | ||
#endif | ||
|
||
@propertyWrapper | ||
final class ThreadLocal<Value>: Sendable { | ||
var wrappedValue: Value? { | ||
get { | ||
guard let pointer = pthread_getspecific(key) else { | ||
return nil | ||
} | ||
return fromPointer(pointer) | ||
} | ||
set { | ||
if let oldPointer = pthread_getspecific(key) { | ||
release(oldPointer) | ||
} | ||
if let newValue = newValue { | ||
let pointer = toPointer(newValue) | ||
pthread_setspecific(key, pointer) | ||
} | ||
} | ||
} | ||
|
||
private let key: pthread_key_t | ||
private let toPointer: @Sendable (Value) -> UnsafeMutableRawPointer | ||
private let fromPointer: @Sendable (UnsafeMutableRawPointer) -> Value | ||
private let release: @Sendable (UnsafeMutableRawPointer) -> Void | ||
|
||
init() where Value: AnyObject { | ||
var key = pthread_key_t() | ||
pthread_key_create(&key, nil) | ||
self.key = key | ||
self.toPointer = { Unmanaged.passRetained($0).toOpaque() } | ||
self.fromPointer = { Unmanaged<Value>.fromOpaque($0).takeUnretainedValue() } | ||
self.release = { Unmanaged<Value>.fromOpaque($0).release() } | ||
} | ||
|
||
class Box { | ||
let value: Value | ||
init(_ value: Value) { | ||
self.value = value | ||
} | ||
} | ||
|
||
init(boxing _: Void) { | ||
var key = pthread_key_t() | ||
pthread_key_create(&key, nil) | ||
self.key = key | ||
self.toPointer = { | ||
let box = Box($0) | ||
let pointer = Unmanaged.passRetained(box).toOpaque() | ||
return pointer | ||
} | ||
self.fromPointer = { | ||
let box = Unmanaged<Box>.fromOpaque($0).takeUnretainedValue() | ||
return box.value | ||
} | ||
self.release = { Unmanaged<Box>.fromOpaque($0).release() } | ||
} | ||
|
||
deinit { | ||
if let oldPointer = pthread_getspecific(key) { | ||
release(oldPointer) | ||
} | ||
pthread_key_delete(key) | ||
} | ||
} | ||
|
||
@propertyWrapper | ||
final class LazyThreadLocal<Value>: Sendable { | ||
private let storage: ThreadLocal<Value> | ||
|
||
var wrappedValue: Value { | ||
if let value = storage.wrappedValue { | ||
return value | ||
} | ||
let value = initialValue() | ||
storage.wrappedValue = value | ||
return value | ||
} | ||
|
||
private let initialValue: @Sendable () -> Value | ||
|
||
init(initialize: @Sendable @escaping () -> Value) where Value: AnyObject { | ||
self.storage = ThreadLocal() | ||
self.initialValue = initialize | ||
} | ||
|
||
init(initialize: @Sendable @escaping () -> Value) { | ||
self.storage = ThreadLocal(boxing: ()) | ||
self.initialValue = initialize | ||
} | ||
} | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import XCTest | ||
@testable import JavaScriptKit | ||
|
||
final class ThreadLocalTests: XCTestCase { | ||
class MyHeapObject {} | ||
|
||
func testLeak() throws { | ||
struct Check { | ||
@ThreadLocal | ||
var value: MyHeapObject? | ||
} | ||
weak var weakObject: MyHeapObject? | ||
do { | ||
let object = MyHeapObject() | ||
weakObject = object | ||
let check = Check() | ||
check.value = object | ||
XCTAssertNotNil(check.value) | ||
XCTAssertTrue(check.value === object) | ||
} | ||
XCTAssertNil(weakObject) | ||
} | ||
|
||
func testLazyThreadLocal() throws { | ||
struct Check { | ||
@LazyThreadLocal(initialize: { MyHeapObject() }) | ||
var value: MyHeapObject | ||
} | ||
let check = Check() | ||
let object1 = check.value | ||
let object2 = check.value | ||
XCTAssertTrue(object1 === object2) | ||
} | ||
} |