Skip to content

Commit 50ba383

Browse files
committed
added withLock to FileSystem
1 parent 7da33fa commit 50ba383

File tree

1 file changed

+35
-0
lines changed

1 file changed

+35
-0
lines changed

Sources/TSCBasic/FileSystem.swift

Lines changed: 35 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 dispatch queue for accessing the lock map in a thread safe manner.
519+
private let lockMapQueue = DispatchQueue(label: "org.swift.swiftpm.\(InMemoryFileSystem.self).lockQueue")
520+
/// A map that keeps weak references to all locked files.
521+
private let lockMap = NSMapTable<NSString, ReadWriteLock>(keyOptions: .copyIn, valueOptions: .weakMemory)
505522

506523
public init() {
507524
root = Node(.directory(DirectoryContents()))
@@ -760,6 +777,20 @@ 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 fileLock: ReadWriteLock?
783+
784+
lockMapQueue.sync {
785+
if let lock = lockMap.object(forKey: path.pathString as NSString) {
786+
fileLock = lock
787+
} else {
788+
fileLock = ReadWriteLock()
789+
lockMap.setObject(fileLock, forKey: path.pathString as NSString)
790+
}
791+
}
792+
return try fileLock!.withLock(type: type, body)
793+
}
763794
}
764795

765796
/// A rerooted view on an existing FileSystem.
@@ -864,6 +895,10 @@ public class RerootedFileSystemView: FileSystem {
864895
public func move(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws {
865896
try underlyingFileSystem.move(from: formUnderlyingPath(sourcePath), to: formUnderlyingPath(sourcePath))
866897
}
898+
899+
public func withLock<T>(on path: AbsolutePath, type: LockType = .exclusive, _ body: () throws -> T) throws -> T {
900+
return try underlyingFileSystem.withLock(on: formUnderlyingPath(path), type: type, body)
901+
}
867902
}
868903

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

0 commit comments

Comments
 (0)