Skip to content

Add advisory file locking API #111

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

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
wip: cleanup
  • Loading branch information
milseman committed Nov 15, 2022
commit df8f08566f8ba771cfb0ab1beb385b1a3378d92b
15 changes: 5 additions & 10 deletions Sources/System/FileLock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,15 @@ extension FileDescriptor.FileLock {
///
/// The corresponding C field is `l_pid`
@_alwaysEmitIntoClient
public var pid: CInterop.PID {
get { rawValue.l_pid }
set { rawValue.l_pid = newValue }
public var pid: ProcessID {
get { ProcessID(rawValue: rawValue.l_pid) }
set { rawValue.l_pid = newValue.rawValue }
}
}

// MARK: - Convenience for `struct flock`
extension FileDescriptor.FileLock {

// For whole-file OFD locks
// For OFD locks
internal init(
ofdType: Kind,
start: Int64,
Expand All @@ -89,12 +88,8 @@ extension FileDescriptor.FileLock {
self.type = ofdType
self.start = start
self.length = length
self.pid = 0
self.pid = ProcessID(rawValue: 0)
}

// TOOO: convenience initializers or static constructors


}

extension FileDescriptor.FileLock {
Expand Down
11 changes: 11 additions & 0 deletions Sources/System/ProcessID.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

/// TODO: docs
@frozen
public struct ProcessID: RawRepresentable, Hashable {
@_alwaysEmitIntoClient
public var rawValue: CInterop.PID

@_alwaysEmitIntoClient
public init(rawValue: CInterop.PID) { self.rawValue = rawValue }
}

80 changes: 43 additions & 37 deletions Tests/SystemTests/FileLockTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import XCTest
@testable import System
#endif

func _range(_ r: some RangeExpression<Int64>) -> Range<Int64> {
r.relative(to: Int64.min..<Int64.max)
}

extension FileOperationsTest {

func testFileLocks() throws {
Expand All @@ -28,12 +32,24 @@ extension FileOperationsTest {
path, .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite)
let dup_2 = try ofd_2.duplicate()

func testOFDs(one: FileDescriptor.FileLock.Kind, two: FileDescriptor.FileLock.Kind) {
XCTAssertEqual(one, try ofd_1.getConflictingLock())
XCTAssertEqual(one, try dup_1.getConflictingLock())

XCTAssertEqual(two, try ofd_2.getConflictingLock())
XCTAssertEqual(two, try dup_2.getConflictingLock())
func testOFDs(
one: FileDescriptor.FileLock.Kind,
two: FileDescriptor.FileLock.Kind,
byteRange: Range<Int64>? = nil
) {
if let br = byteRange {
XCTAssertEqual(one, try ofd_1.getConflictingLock(byteRange: br))
XCTAssertEqual(one, try dup_1.getConflictingLock(byteRange: br))

XCTAssertEqual(two, try ofd_2.getConflictingLock(byteRange: br))
XCTAssertEqual(two, try dup_2.getConflictingLock(byteRange: br))
} else {
XCTAssertEqual(one, try ofd_1.getConflictingLock())
XCTAssertEqual(one, try dup_1.getConflictingLock())

XCTAssertEqual(two, try ofd_2.getConflictingLock())
XCTAssertEqual(two, try dup_2.getConflictingLock())
}
}

testOFDs(one: .none, two: .none)
Expand Down Expand Up @@ -65,42 +81,32 @@ extension FileOperationsTest {
}

try ofd_1.unlock()
XCTAssertEqual(.read, try ofd_1.getConflictingLock())
XCTAssertEqual(.read, try dup_1.getConflictingLock())
XCTAssertEqual(.none, try ofd_2.getConflictingLock())
XCTAssertEqual(.none, try dup_2.getConflictingLock())

try dup_2.lock(.write, nonBlocking: true)
XCTAssertEqual(.write, try ofd_1.getConflictingLock())
XCTAssertEqual(.write, try dup_1.getConflictingLock())
XCTAssertEqual(.none, try ofd_2.getConflictingLock())
XCTAssertEqual(.none, try dup_2.getConflictingLock())
}

func testFileLockByteRanges() throws {
let path = FilePath("/tmp/\(UUID().uuidString).txt")
try ofd_2.unlock()
testOFDs(one: .none, two: .none)

let ofd_1 = try FileDescriptor.open(
path, .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite)
let dup_1 = try ofd_1.duplicate()
/// Byte ranges

let ofd_2 = try FileDescriptor.open(
path, .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite)
let dup_2 = try ofd_2.duplicate()
try dup_1.lock(byteRange: ..<50)
testOFDs(one: .none, two: .read)
testOFDs(one: .none, two: .none, byteRange: _range(51...))
testOFDs(one: .none, two: .read, byteRange: _range(1..<2))

func testOFDs(
one: FileDescriptor.FileLock.Kind,
two: FileDescriptor.FileLock.Kind,
byteRange range: Range<Int64>
) {
XCTAssertEqual(one, try ofd_1.getConflictingLock(byteRange: range))
XCTAssertEqual(one, try dup_1.getConflictingLock(byteRange: range))
try dup_1.lock(.write, byteRange: 100..<150)
testOFDs(one: .none, two: .write)
testOFDs(one: .none, two: .read, byteRange: 49..<50)
testOFDs(one: .none, two: .none, byteRange: 98..<99)
testOFDs(one: .none, two: .write, byteRange: _range(100...))

XCTAssertEqual(two, try ofd_2.getConflictingLock(byteRange: range))
XCTAssertEqual(two, try dup_2.getConflictingLock(byteRange: range))
}
try dup_1.unlock(byteRange: ..<49)
testOFDs(one: .none, two: .read, byteRange: 49..<50)

// TODO: tests
try dup_1.unlock(byteRange: ..<149)
testOFDs(one: .none, two: .write)
testOFDs(one: .none, two: .none, byteRange: _range(..<149))
testOFDs(one: .none, two: .write, byteRange: 149..<150)

try dup_1.unlock(byteRange: 149..<150)
testOFDs(one: .none, two: .none)
}
}