Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add async overloads of withLock for FS I/O #420

Merged
merged 2 commits into from Jun 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 21 additions & 6 deletions Sources/TSCBasic/FileSystem.swift
Expand Up @@ -288,6 +288,9 @@ public protocol FileSystem: Sendable {

/// Execute the given block while holding the lock.
func withLock<T>(on path: AbsolutePath, type: FileLock.LockType, _ body: () throws -> T) throws -> T

/// Execute the given block while holding the lock.
func withLock<T>(on path: AbsolutePath, type: FileLock.LockType, _ body: () async throws -> T) async throws -> T
}

/// Convenience implementations (default arguments aren't permitted in protocol
Expand Down Expand Up @@ -336,6 +339,10 @@ public extension FileSystem {
throw FileSystemError(.unsupported, path)
}

func withLock<T>(on path: AbsolutePath, type: FileLock.LockType, _ body: () async throws -> T) async throws -> T {
throw FileSystemError(.unsupported, path)
}

func hasQuarantineAttribute(_ path: AbsolutePath) -> Bool { false }

func hasAttribute(_ name: FileSystemAttribute, _ path: AbsolutePath) -> Bool { false }
Expand Down Expand Up @@ -601,12 +608,20 @@ private struct LocalFileSystem: FileSystem {
func withLock<T>(on path: AbsolutePath, type: FileLock.LockType = .exclusive, _ body: () throws -> T) throws -> T {
try FileLock.withLock(fileToLock: path, type: type, body: body)
}

func withLock<T>(
on path: AbsolutePath,
type: FileLock.LockType = .exclusive,
_ body: () async throws -> T
) async throws -> T {
try await FileLock.withLock(fileToLock: path, type: type, body: body)
}
}

/// Concrete FileSystem implementation which simulates an empty disk.
public final class InMemoryFileSystem: FileSystem {
/// Private internal representation of a file system node.
/// Not threadsafe.
/// Not thread-safe.
private class Node {
/// The actual node data.
let contents: NodeContents
Expand All @@ -622,7 +637,7 @@ public final class InMemoryFileSystem: FileSystem {
}

/// Private internal representation the contents of a file system node.
/// Not threadsafe.
/// Not thread-safe.
private enum NodeContents {
case file(ByteString)
case directory(DirectoryContents)
Expand All @@ -642,7 +657,7 @@ public final class InMemoryFileSystem: FileSystem {
}

/// Private internal representation the contents of a directory.
/// Not threadsafe.
/// Not thread-safe.
private final class DirectoryContents {
var entries: [String: Node]

Expand Down Expand Up @@ -697,7 +712,7 @@ public final class InMemoryFileSystem: FileSystem {
}

/// Private function to look up the node corresponding to a path.
/// Not threadsafe.
/// Not thread-safe.
private func getNode(_ path: AbsolutePath, followSymlink: Bool = true) throws -> Node? {
func getNodeInternal(_ path: AbsolutePath) throws -> Node? {
// If this is the root node, return it.
Expand Down Expand Up @@ -841,7 +856,7 @@ public final class InMemoryFileSystem: FileSystem {
}
}

/// Not threadsafe.
/// Not thread-safe.
private func _createDirectory(_ path: AbsolutePath, recursive: Bool) throws {
// Ignore if client passes root.
guard !path.isRoot else {
Expand Down Expand Up @@ -989,7 +1004,7 @@ public final class InMemoryFileSystem: FileSystem {
}

/// Private implementation of core copying function.
/// Not threadsafe.
/// Not thread-safe.
private func _copy(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws {
// Get the source node.
guard let source = try getNode(sourcePath) else {
Expand Down
36 changes: 33 additions & 3 deletions Sources/TSCBasic/Lock.swift
Expand Up @@ -177,8 +177,19 @@ public final class FileLock {
defer { unlock() }
return try body()
}

public static func withLock<T>(fileToLock: AbsolutePath, lockFilesDirectory: AbsolutePath? = nil, type: LockType = .exclusive, body: () throws -> T) throws -> T {

/// Execute the given block while holding the lock.
public func withLock<T>(type: LockType = .exclusive, _ body: () async throws -> T) async throws -> T {
try lock(type: type)
defer { unlock() }
return try await body()
}

private static func prepareLock(
fileToLock: AbsolutePath,
at lockFilesDirectory: AbsolutePath? = nil,
_ type: LockType = .exclusive
) throws -> FileLock {
// unless specified, we use the tempDirectory to store lock files
let lockFilesDirectory = try lockFilesDirectory ?? localFileSystem.tempDirectory
if !localFileSystem.exists(lockFilesDirectory) {
Expand Down Expand Up @@ -215,7 +226,26 @@ public final class FileLock {
#endif
let lockFilePath = lockFilesDirectory.appending(component: lockFileName)

let lock = FileLock(at: lockFilePath)
return FileLock(at: lockFilePath)
}

public static func withLock<T>(
fileToLock: AbsolutePath,
lockFilesDirectory: AbsolutePath? = nil,
type: LockType = .exclusive,
body: () throws -> T
) throws -> T {
let lock = try Self.prepareLock(fileToLock: fileToLock, at: lockFilesDirectory, type)
return try lock.withLock(type: type, body)
}

public static func withLock<T>(
fileToLock: AbsolutePath,
lockFilesDirectory: AbsolutePath? = nil,
type: LockType = .exclusive,
body: () async throws -> T
) async throws -> T {
let lock = try Self.prepareLock(fileToLock: fileToLock, at: lockFilesDirectory, type)
return try await lock.withLock(type: type, body)
}
}