Skip to content

Commit 031428b

Browse files
committed
added property wrapper @ThreadLocal
1 parent 2c2be2b commit 031428b

File tree

3 files changed

+63
-17
lines changed

3 files changed

+63
-17
lines changed

Sources/TSCBasic/Lock.swift

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ enum ProcessLockError: Swift.Error {
8080
public final class FileLock {
8181
/// File descriptor to the lock file.
8282
#if os(Windows)
83-
private var handle: HANDLE?
83+
@ThreadLocal @AutoClosing private var handle: HANDLE?
8484
#else
85-
private var fileDescriptor: CInt?
85+
@ThreadLocal @AutoClosing private var fileDescriptor: CInt?
8686
#endif
8787

8888
/// Path to the lock file.
@@ -179,21 +179,11 @@ public final class FileLock {
179179
overlapped.hEvent = nil
180180
UnlockFileEx(handle, 0, DWORD(INT_MAX), DWORD(INT_MAX), &overlapped)
181181
#else
182-
guard let fd = fileDescriptor else { return }
182+
guard let fd = fileDescriptor else { return }
183183
flock(fd, LOCK_UN)
184184
#endif
185185
}
186186

187-
deinit {
188-
#if os(Windows)
189-
guard let handle = handle else { return }
190-
CloseHandle(handle)
191-
#else
192-
guard let fd = fileDescriptor else { return }
193-
close(fd)
194-
#endif
195-
}
196-
197187
/// Execute the given block while holding the lock.
198188
public func withLock<T>(type: LockType = .exclusive, _ body: () throws -> T) throws -> T {
199189
try lock(type: type)

Sources/TSCBasic/Thread.swift

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010

1111
import Foundation
12+
import TSCLibc
1213

1314
/// This class bridges the gap between Darwin and Linux Foundation Threading API.
1415
/// It provides closure based execution and a join method to block the calling thread
@@ -94,3 +95,60 @@ final private class ThreadImpl: Foundation.Thread {
9495
// Thread on Linux supports closure so just use it directly.
9596
typealias ThreadImpl = Foundation.Thread
9697
#endif
98+
99+
protocol Defaultable {
100+
static var defaultValue: Self { get }
101+
}
102+
103+
extension Optional: Defaultable {
104+
static var defaultValue: Optional<Wrapped> { .none }
105+
}
106+
107+
/// `ThreadLocal` properties are thread-specific. Every thread has its own instance of the wrapped property.
108+
@propertyWrapper final class ThreadLocal<Value: Defaultable> {
109+
private var storage: NSMutableDictionary { ThreadImpl.current.threadDictionary }
110+
private let key = UUID().uuidString
111+
112+
var wrappedValue: Value {
113+
get {
114+
if let value = storage[key] as? Value {
115+
return value
116+
} else {
117+
let value = Value.defaultValue
118+
storage[key] = value
119+
return value
120+
}
121+
}
122+
set { storage[key] = newValue }
123+
}
124+
125+
init(wrappedValue: Value) {
126+
self.wrappedValue = wrappedValue
127+
}
128+
}
129+
130+
/// Automatically closes the wrapped file descriptor on deinit.
131+
@propertyWrapper final class AutoClosing: NSObject, Defaultable {
132+
#if os(Windows)
133+
typealias T = HANDLE
134+
#else
135+
typealias T = CInt
136+
#endif
137+
var wrappedValue: T?
138+
139+
static var defaultValue: AutoClosing { AutoClosing(wrappedValue: .none) }
140+
141+
init(wrappedValue: T?) {
142+
self.wrappedValue = wrappedValue
143+
}
144+
145+
deinit {
146+
if let wrappedValue = wrappedValue {
147+
#if os(Windows)
148+
CloseHandle(wrappedValue)
149+
#else
150+
close(wrappedValue)
151+
#endif
152+
}
153+
}
154+
}

Tests/TSCBasicTests/LockTests.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ class LockTests: XCTestCase {
9090

9191
let writerThreads = (0..<100).map { _ in
9292
return Thread {
93-
let lock = FileLock(name: "foo", cachePath: tempDir)
9493
try! lock.withLock(type: .exclusive) {
9594
// Get thr current contents of the file if any.
9695
let valueA: Int
@@ -119,13 +118,12 @@ class LockTests: XCTestCase {
119118

120119
let readerThreads = (0..<20).map { _ in
121120
return Thread {
122-
let lock = FileLock(name: "foo", cachePath: tempDir)
123121
try! lock.withLock(type: .shared) {
124-
try XCTAssertEqual( localFileSystem.readFileContents(fileA), localFileSystem.readFileContents(fileB))
122+
try XCTAssertEqual(localFileSystem.readFileContents(fileA), localFileSystem.readFileContents(fileB))
125123

126124
Thread.yield()
127125

128-
try XCTAssertEqual( localFileSystem.readFileContents(fileA), localFileSystem.readFileContents(fileB))
126+
try XCTAssertEqual(localFileSystem.readFileContents(fileA), localFileSystem.readFileContents(fileB))
129127
}
130128
}
131129
}

0 commit comments

Comments
 (0)