@@ -127,11 +127,11 @@ extension FileDescriptor.FileLock {
127127}
128128
129129extension FileDescriptor {
130- /// All bytes in a file
131- @ _alwaysEmitIntoClient
132- internal var _allFileBytes : Range < Int64 > { Int64 . min ..< Int64 . max }
133-
134- /// Get any conflicting locks held by other open file descriptions .
130+ /// Set an advisory open file description lock.
131+ ///
132+ /// If the open file description already has a lock, the old lock is
133+ /// replaced. If the lock cannot be set because it is blocked by an existing lock,
134+ /// this will wait until the lock can be set .
135135 ///
136136 /// Open file description locks are associated with an open file
137137 /// description (see `FileDescriptor.open`). Duplicated
@@ -146,69 +146,92 @@ extension FileDescriptor {
146146 /// Open file description locks are inherited by child processes across
147147 /// `fork`, etc.
148148 ///
149+ /// Passing a lock kind of `.none` will remove a lock (equivalent to calling
150+ /// `FileDescriptor.unlock()`).
151+ ///
149152 /// - Parameters:
150- /// - byteRange: The range of bytes over which to check for a lock. Pass
153+ /// - kind: The kind of lock to set
154+ /// - byteRange: The range of bytes over which to lock. Pass
151155 /// `nil` to consider the entire file.
152156 /// - retryOnInterrupt: Whether to retry the operation if it throws
153157 /// ``Errno/interrupted``. The default is `true`. Pass `false` to try
154158 /// only once and throw an error upon interruption.
155- /// - Returns; `.none` if there are no locks, otherwise returns the
156- /// strongest conflicting lock
157159 ///
158- /// The corresponding C function is `fcntl` with `F_OFD_GETLK `.
160+ /// The corresponding C function is `fcntl` with `F_OFD_SETLKW `.
159161 @_alwaysEmitIntoClient
160- public func getConflictingLock(
162+ public func lock(
163+ _ kind: FileDescriptor . FileLock . Kind = . read,
161164 byteRange: ( some RangeExpression < Int64 > ) ? = Range ? . none,
162165 retryOnInterrupt: Bool = true
163- ) throws -> FileDescriptor . FileLock . Kind {
166+ ) throws {
164167 let ( start, len) = _mapByteRangeToByteOffsets ( byteRange)
165- return try _getConflictingLock (
166- start: start, length: len, retryOnInterrupt: retryOnInterrupt
168+ try _lock (
169+ kind,
170+ start: start,
171+ length: len,
172+ retryOnInterrupt: retryOnInterrupt
167173 ) . get ( )
168174 }
169175
170- @usableFromInline
171- internal func _getConflictingLock(
172- start: Int64 , length: Int64 , retryOnInterrupt: Bool
173- ) -> Result < FileDescriptor . FileLock . Kind , Errno > {
174- // If there are multiple locks already in place on a file region, the lock that
175- // is returned is unspecified. E.g. there could be a write lock over one
176- // portion of the file and a read lock over another overlapping
177- // region. Thus, we first check if there are any write locks, and if not
178- // we issue another call to check for any reads-or-writes.
179- //
180- // 1) Try with a read lock, which will tell us if there's a conflicting
181- // write lock in place.
182- //
183- // 2) Try with a write lock, which will tell us if there's either a
184- // conflicting read or write lock in place.
185- var lock = FileDescriptor . FileLock ( ofdType: . read, start: start, length: length)
186- if case let . failure( err) = self . _fcntl (
187- . getOFDLock, & lock, retryOnInterrupt: retryOnInterrupt
188- ) {
189- return . failure( err)
190- }
191- if lock. type == . write {
192- return . success( . write)
193- }
194- guard lock. type == . none else {
195- fatalError ( " FIXME: really shouldn't be possible " )
176+ /// Try to set an advisory open file description lock.
177+ ///
178+ /// If the open file description already has a lock, the old lock is
179+ /// replaced. If the lock cannot be set because it is blocked by an existing lock,
180+ /// that is if the syscall would throw `.resourceTemporarilyUnavailable`
181+ /// (aka `EAGAIN`), this will return `false`.
182+ ///
183+ /// Open file description locks are associated with an open file
184+ /// description (see `FileDescriptor.open`). Duplicated
185+ /// file descriptors (see `FileDescriptor.duplicate`) share open file
186+ /// description locks.
187+ ///
188+ /// Locks are advisory, which allow cooperating code to perform
189+ /// consistent operations on files, but do not guarantee consistency.
190+ /// (i.e. other code may still access files without using advisory locks
191+ /// possibly resulting in inconsistencies).
192+ ///
193+ /// Open file description locks are inherited by child processes across
194+ /// `fork`, etc.
195+ ///
196+ /// Passing a lock kind of `.none` will remove a lock (equivalent to calling
197+ /// `FileDescriptor.unlock()`).
198+ ///
199+ /// - Parameters:
200+ /// - kind: The kind of lock to set
201+ /// - byteRange: The range of bytes over which to lock. Pass
202+ /// `nil` to consider the entire file.
203+ /// - retryOnInterrupt: Whether to retry the operation if it throws
204+ /// ``Errno/interrupted``. The default is `true`. Pass `false` to try
205+ /// only once and throw an error upon interruption.
206+ /// - Returns: `true` if the lock was aquired, `false` otherwise
207+ ///
208+ /// The corresponding C function is `fcntl` with `F_OFD_SETLK`.
209+ @_alwaysEmitIntoClient
210+ public func tryLock(
211+ _ kind: FileDescriptor . FileLock . Kind = . read,
212+ byteRange: ( some RangeExpression < Int64 > ) ? = Range ? . none,
213+ retryOnInterrupt: Bool = true
214+ ) throws -> Bool {
215+ let ( start, len) = _mapByteRangeToByteOffsets ( byteRange)
216+ guard let _ = try _tryLock (
217+ kind,
218+ waitUntilTimeout: false ,
219+ start: start,
220+ length: len,
221+ retryOnInterrupt: retryOnInterrupt
222+ ) ? . get ( ) else {
223+ return false
196224 }
197- // This means there was no conflicting lock, so try to detect reads
198- lock = FileDescriptor . FileLock ( ofdType: . write, start: start, length: length)
199-
200- let secondTry = self . _fcntl ( . getOFDLock, & lock, retryOnInterrupt: retryOnInterrupt)
201- return secondTry. map { lock. type }
225+ return true
202226 }
203227
204- /// Set an open file description lock.
228+ #if !os(Linux)
229+ /// Try to set an advisory open file description lock.
205230 ///
206231 /// If the open file description already has a lock, the old lock is
207- /// replaced.
208- ///
209- /// If the lock cannot be set because it is blocked by an existing lock on a
210- /// file and `wait` is `false`,
211- /// `Errno.resourceTemporarilyUnavailable` is thrown.
232+ /// replaced. If the lock cannot be set because it is blocked by an existing lock,
233+ /// that is if the syscall would throw `.resourceTemporarilyUnavailable`
234+ /// (aka `EAGAIN`), this will return `false`.
212235 ///
213236 /// Open file description locks are associated with an open file
214237 /// description (see `FileDescriptor.open`). Duplicated
@@ -230,29 +253,33 @@ extension FileDescriptor {
230253 /// - kind: The kind of lock to set
231254 /// - byteRange: The range of bytes over which to lock. Pass
232255 /// `nil` to consider the entire file.
233- /// - wait: Whether to wait (block) until the request can be completed
256+ /// - waitUntilTimeout: If `true`, will wait until a timeout (determined by the operating system)
234257 /// - retryOnInterrupt: Whether to retry the operation if it throws
235258 /// ``Errno/interrupted``. The default is `true`. Pass `false` to try
236259 /// only once and throw an error upon interruption.
260+ /// - Returns: `true` if the lock was aquired, `false` otherwise
237261 ///
238- /// The corresponding C function is `fcntl` with `F_OFD_SETLK` or
239- /// `F_OFD_SETLKW`.
262+ /// The corresponding C function is `fcntl` with `F_OFD_SETLK` or `F_OFD_SETLKWTIMEOUT` .
240263 @_alwaysEmitIntoClient
241- public func lock (
264+ public func tryLock (
242265 _ kind: FileDescriptor . FileLock . Kind = . read,
243266 byteRange: ( some RangeExpression < Int64 > ) ? = Range ? . none,
244- wait : Bool = false ,
267+ waitUntilTimeout : Bool ,
245268 retryOnInterrupt: Bool = true
246- ) throws {
269+ ) throws -> Bool {
247270 let ( start, len) = _mapByteRangeToByteOffsets ( byteRange)
248- try _lock (
271+ guard let _ = try _tryLock (
249272 kind,
273+ waitUntilTimeout: waitUntilTimeout,
250274 start: start,
251275 length: len,
252- wait: wait,
253276 retryOnInterrupt: retryOnInterrupt
254- ) . get ( )
277+ ) ? . get ( ) else {
278+ return false
279+ }
280+ return true
255281 }
282+ #endif
256283
257284 /// Remove an open file description lock.
258285 ///
@@ -275,7 +302,6 @@ extension FileDescriptor {
275302 /// - Parameters:
276303 /// - byteRange: The range of bytes over which to lock. Pass
277304 /// `nil` to consider the entire file.
278- /// - wait: Whether to wait (block) until the request can be completed
279305 /// - retryOnInterrupt: Whether to retry the operation if it throws
280306 /// ``Errno/interrupted``. The default is `true`. Pass `false` to try
281307 /// only once and throw an error upon interruption.
@@ -289,27 +315,52 @@ extension FileDescriptor {
289315 retryOnInterrupt: Bool = true
290316 ) throws {
291317 let ( start, len) = _mapByteRangeToByteOffsets ( byteRange)
292- try _lock (
318+ guard let res = _tryLock (
293319 . none,
320+ waitUntilTimeout: false , // TODO: or we wait for timeout?
294321 start: start,
295322 length: len,
296- wait: wait,
297323 retryOnInterrupt: retryOnInterrupt
298- ) . get ( )
324+ ) else {
325+ preconditionFailure ( " TODO: Unlock should always succeed? " )
326+ }
327+ return try res. get ( )
299328 }
300329
301330 @usableFromInline
302331 internal func _lock(
303332 _ kind: FileDescriptor . FileLock . Kind ,
304333 start: Int64 ,
305334 length: Int64 ,
306- wait: Bool ,
307335 retryOnInterrupt: Bool
308336 ) -> Result < ( ) , Errno > {
309- var lock = FileDescriptor . FileLock ( ofdType: kind, start: start, length: length)
310- let command : FileDescriptor . Control . Command =
311- wait ? . setOFDLockWait : . setOFDLock
312- return _fcntl ( command, & lock, retryOnInterrupt: retryOnInterrupt)
337+ var lock = FileDescriptor . FileLock (
338+ ofdType: kind, start: start, length: length)
339+ return _fcntl ( . setOFDLockWait, & lock, retryOnInterrupt: retryOnInterrupt)
340+ }
341+
342+ @usableFromInline
343+ internal func _tryLock(
344+ _ kind: FileDescriptor . FileLock . Kind ,
345+ waitUntilTimeout: Bool ,
346+ start: Int64 ,
347+ length: Int64 ,
348+ retryOnInterrupt: Bool
349+ ) -> Result < ( ) , Errno > ? {
350+ #if os(Linux)
351+ precondition ( !waitUntilTimeout, " `waitUntilTimeout` unavailable on Linux " )
352+ #endif
353+
354+ let cmd : Control . Command
355+ if waitUntilTimeout {
356+ cmd = . setOFDLockWaitTimout
357+ } else {
358+ cmd = . setOFDLock
359+ }
360+ var lock = FileDescriptor . FileLock (
361+ ofdType: kind, start: start, length: length)
362+ return _extractWouldBlock (
363+ _fcntl ( cmd, & lock, retryOnInterrupt: retryOnInterrupt) )
313364 }
314365}
315366#endif
0 commit comments