|
10 | 10 |
|
11 | 11 | import TSCLibc
|
12 | 12 | import Foundation
|
| 13 | +import Dispatch |
13 | 14 |
|
14 | 15 | public enum FileSystemError: Swift.Error {
|
15 | 16 | /// Access to the path is denied.
|
@@ -200,6 +201,9 @@ public protocol FileSystem: class {
|
200 | 201 |
|
201 | 202 | /// Move a file or directory.
|
202 | 203 | 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 |
203 | 207 | }
|
204 | 208 |
|
205 | 209 | /// Convenience implementations (default arguments aren't permitted in protocol
|
@@ -240,6 +244,10 @@ public extension FileSystem {
|
240 | 244 | func getFileInfo(_ path: AbsolutePath) throws -> FileInfo {
|
241 | 245 | throw FileSystemError.unsupported
|
242 | 246 | }
|
| 247 | + |
| 248 | + func withLock<T>(on path: AbsolutePath, type: LockType, _ body: () throws -> T) throws -> T { |
| 249 | + throw FileSystemError.unsupported |
| 250 | + } |
243 | 251 | }
|
244 | 252 |
|
245 | 253 | /// Concrete FileSystem implementation which communicates with the local file system.
|
@@ -450,6 +458,11 @@ private class LocalFileSystem: FileSystem {
|
450 | 458 | guard !exists(destinationPath) else { throw FileSystemError.alreadyExistsAtDestination }
|
451 | 459 | try FileManager.default.moveItem(at: sourcePath.asURL, to: destinationPath.asURL)
|
452 | 460 | }
|
| 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 | + } |
453 | 466 | }
|
454 | 467 |
|
455 | 468 | // FIXME: This class does not yet support concurrent mutation safely.
|
@@ -502,6 +515,10 @@ public class InMemoryFileSystem: FileSystem {
|
502 | 515 |
|
503 | 516 | /// The root filesytem.
|
504 | 517 | 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) |
505 | 522 |
|
506 | 523 | public init() {
|
507 | 524 | root = Node(.directory(DirectoryContents()))
|
@@ -760,6 +777,20 @@ public class InMemoryFileSystem: FileSystem {
|
760 | 777 |
|
761 | 778 | contents.entries[sourcePath.basename] = nil
|
762 | 779 | }
|
| 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 | + } |
763 | 794 | }
|
764 | 795 |
|
765 | 796 | /// A rerooted view on an existing FileSystem.
|
@@ -864,6 +895,10 @@ public class RerootedFileSystemView: FileSystem {
|
864 | 895 | public func move(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws {
|
865 | 896 | try underlyingFileSystem.move(from: formUnderlyingPath(sourcePath), to: formUnderlyingPath(sourcePath))
|
866 | 897 | }
|
| 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 | + } |
867 | 902 | }
|
868 | 903 |
|
869 | 904 | /// Public access to the local FS proxy.
|
|
0 commit comments