Skip to content

Commit bc582aa

Browse files
committed
added withLock to FileSystem
1 parent 1bbb155 commit bc582aa

File tree

5 files changed

+82
-91
lines changed

5 files changed

+82
-91
lines changed

Sources/TSCBasic/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ add_library(TSCBasic
4848
TerminalController.swift
4949
Thread.swift
5050
Tuple.swift
51-
misc.swift)
51+
misc.swift
52+
WeakDictionary.swift)
53+
5254
target_compile_options(TSCBasic PUBLIC
5355
# Don't use GNU strerror_r on Android.
5456
"$<$<PLATFORM_ID:Android>:SHELL:-Xcc -U_GNU_SOURCE>"

Sources/TSCBasic/FileSystem.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import TSCLibc
1212
import Foundation
13+
import Dispatch
1314

1415
public enum FileSystemError: Swift.Error {
1516
/// Access to the path is denied.
@@ -200,6 +201,9 @@ public protocol FileSystem: class {
200201

201202
/// Move a file or directory.
202203
func move(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws
204+
205+
/// Execute the given block while holding the lock.
206+
func withLock<T>(on path: AbsolutePath, type: LockType, _ body: () throws -> T) throws -> T
203207
}
204208

205209
/// Convenience implementations (default arguments aren't permitted in protocol
@@ -240,6 +244,10 @@ public extension FileSystem {
240244
func getFileInfo(_ path: AbsolutePath) throws -> FileInfo {
241245
throw FileSystemError.unsupported
242246
}
247+
248+
func withLock<T>(on path: AbsolutePath, type: LockType, _ body: () throws -> T) throws -> T {
249+
throw FileSystemError.unsupported
250+
}
243251
}
244252

245253
/// Concrete FileSystem implementation which communicates with the local file system.
@@ -450,6 +458,11 @@ private class LocalFileSystem: FileSystem {
450458
guard !exists(destinationPath) else { throw FileSystemError.alreadyExistsAtDestination }
451459
try FileManager.default.moveItem(at: sourcePath.asURL, to: destinationPath.asURL)
452460
}
461+
462+
func withLock<T>(on path: AbsolutePath, type: LockType = .exclusive, _ body: () throws -> T) throws -> T {
463+
let lock = FileLock(name: path.basename, cachePath: path.parentDirectory)
464+
return try lock.withLock(type: type, body)
465+
}
453466
}
454467

455468
// FIXME: This class does not yet support concurrent mutation safely.
@@ -502,6 +515,10 @@ public class InMemoryFileSystem: FileSystem {
502515

503516
/// The root filesytem.
504517
private var root: Node
518+
/// A map that keeps weak references to all locked files.
519+
private var lockFiles = WeakDictionary<AbsolutePath, DispatchQueue>()
520+
/// Used to access lockFiles in a thread safe manner.
521+
private let lockFilesLock = Lock()
505522

506523
public init() {
507524
root = Node(.directory(DirectoryContents()))
@@ -760,6 +777,21 @@ public class InMemoryFileSystem: FileSystem {
760777

761778
contents.entries[sourcePath.basename] = nil
762779
}
780+
781+
public func withLock<T>(on path: AbsolutePath, type: LockType = .exclusive, _ body: () throws -> T) throws -> T {
782+
var fileQueue: DispatchQueue
783+
784+
lockFilesLock.lock()
785+
if let queue = lockFiles[path] {
786+
fileQueue = queue
787+
} else {
788+
fileQueue = DispatchQueue(label: "foo", attributes: .concurrent)
789+
lockFiles[path] = fileQueue
790+
}
791+
lockFilesLock.unlock()
792+
793+
return try fileQueue.sync(flags: type == .exclusive ? .barrier : .init() , execute: body)
794+
}
763795
}
764796

765797
/// A rerooted view on an existing FileSystem.
@@ -864,6 +896,10 @@ public class RerootedFileSystemView: FileSystem {
864896
public func move(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws {
865897
try underlyingFileSystem.move(from: formUnderlyingPath(sourcePath), to: formUnderlyingPath(sourcePath))
866898
}
899+
900+
public func withLock<T>(on path: AbsolutePath, type: LockType = .exclusive, _ body: () throws -> T) throws -> T {
901+
return try underlyingFileSystem.withLock(on: formUnderlyingPath(path), type: type, body)
902+
}
867903
}
868904

869905
/// Public access to the local FS proxy.

Sources/TSCBasic/Lock.swift

Lines changed: 15 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -24,50 +24,20 @@ public struct Lock {
2424
public init() {
2525
}
2626

27-
/// Execute the given block while holding the lock.
28-
public func withLock<T> (_ body: () throws -> T) rethrows -> T {
27+
func lock() {
2928
_lock.lock()
30-
defer { _lock.unlock() }
31-
return try body()
32-
}
33-
}
34-
35-
/// A lock that allows multiple readers but only one write at the same time.
36-
public class ReadWriteLock {
37-
private var lock = pthread_rwlock_t()
38-
39-
/// Create a new lock.
40-
public init() {
41-
pthread_rwlock_init(&lock, nil)
42-
}
43-
44-
func readLock() {
45-
pthread_rwlock_rdlock(&lock)
46-
}
47-
48-
func writeLock() {
49-
pthread_rwlock_wrlock(&lock)
5029
}
5130

5231
func unlock() {
53-
pthread_rwlock_unlock(&lock)
32+
_lock.unlock()
5433
}
5534

5635
/// Execute the given block while holding the lock.
57-
public func withLock<T>(type lockType: LockType = .exclusive, _ body: () throws -> T) rethrows -> T {
58-
switch lockType {
59-
case .shared:
60-
readLock()
61-
case .exclusive:
62-
writeLock()
63-
}
36+
public func withLock<T> (_ body: () throws -> T) rethrows -> T {
37+
lock()
6438
defer { unlock() }
6539
return try body()
6640
}
67-
68-
deinit {
69-
pthread_rwlock_destroy(&lock)
70-
}
7141
}
7242

7343
enum ProcessLockError: Swift.Error {
@@ -102,29 +72,16 @@ public final class FileLock {
10272
public func lock(type: LockType = .exclusive) throws {
10373
#if os(Windows)
10474
if handle == nil {
105-
let h = lockFile.pathString.withCString(encodedAs: UTF16.self, {
106-
switch mode {
107-
case .exclusive:
108-
CreateFileW(
109-
$0,
110-
UInt32(GENERIC_READ) | UInt32(GENERIC_WRITE),
111-
0,
112-
nil,
113-
DWORD(OPEN_ALWAYS),
114-
DWORD(FILE_ATTRIBUTE_NORMAL),
115-
nil
116-
)
117-
case .shared:
118-
CreateFileW(
119-
$0,
120-
UInt32(GENERIC_READ) | UInt32(GENERIC_WRITE),
121-
DWORD(FILE_SHARE_READ),
122-
nil,
123-
DWORD(OPEN_ALWAYS),
124-
DWORD(FILE_ATTRIBUTE_NORMAL),
125-
nil
126-
)
127-
}
75+
let h: HANDLE = lockFile.pathString.withCString(encodedAs: UTF16.self, {
76+
CreateFileW(
77+
$0,
78+
UInt32(GENERIC_READ) | UInt32(GENERIC_WRITE),
79+
UInt32(FILE_SHARE_READ) | UInt32(FILE_SHARE_WRITE),
80+
nil,
81+
DWORD(OPEN_ALWAYS),
82+
DWORD(FILE_ATTRIBUTE_NORMAL),
83+
nil
84+
)
12885
})
12986
if h == INVALID_HANDLE_VALUE {
13087
throw FileSystemError(errno: Int32(GetLastError()))
@@ -135,7 +92,7 @@ public final class FileLock {
13592
overlapped.Offset = 0
13693
overlapped.OffsetHigh = 0
13794
overlapped.hEvent = nil
138-
switch mode {
95+
switch type {
13996
case .exclusive:
14097
if !LockFileEx(handle, DWORD(LOCKFILE_EXCLUSIVE_LOCK), 0,
14198
DWORD(INT_MAX), DWORD(INT_MAX), &overlapped) {

Sources/TSCBasic/WeakDictionary.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See http://swift.org/LICENSE.txt for license information
8+
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
/// A dictionary that only keeps weak references to its values.
12+
struct WeakDictionary<Key: Hashable, Value: AnyObject> {
13+
14+
private struct WeakReference<Value: AnyObject> {
15+
weak var reference: Value?
16+
17+
init(_ value: Value?) {
18+
self.reference = value
19+
}
20+
}
21+
22+
private var storage = Dictionary<Key, WeakReference<Value>>()
23+
24+
subscript(key: Key) -> Value? {
25+
get { storage[key]?.reference }
26+
set(newValue) { storage[key] = WeakReference(newValue) }
27+
}
28+
}

Tests/TSCBasicTests/LockTests.swift

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -49,38 +49,6 @@ class LockTests: XCTestCase {
4949
}
5050
}
5151

52-
func testReadWriteLock() throws {
53-
var a = 0
54-
var b = 0
55-
56-
let lock = ReadWriteLock()
57-
58-
let writerThreads = (0..<100).map { _ in
59-
return Thread {
60-
lock.withLock(type: .exclusive) {
61-
a+=1
62-
sched_yield()
63-
b+=1
64-
}
65-
}
66-
}
67-
68-
let readerThreads = (0..<20).map { _ in
69-
return Thread {
70-
lock.withLock(type: .shared) {
71-
XCTAssertEqual(a,b)
72-
sched_yield()
73-
XCTAssertEqual(a,b)
74-
}
75-
}
76-
}
77-
78-
writerThreads.forEach { $0.start() }
79-
readerThreads.forEach { $0.start() }
80-
writerThreads.forEach { $0.join() }
81-
readerThreads.forEach { $0.join() }
82-
}
83-
8452
func testReadWriteFileLock() throws {
8553
try withTemporaryDirectory { tempDir in
8654
let fileA = tempDir.appending(component: "fileA")

0 commit comments

Comments
 (0)