@@ -123,6 +123,18 @@ extension FileDescriptor.FileLock {
123123 public static var none : Self {
124124 Self ( rawValue: CInterop . CShort ( truncatingIfNeeded: F_UNLCK) )
125125 }
126+
127+ /// Shared (alias for `read`)
128+ @_alwaysEmitIntoClient
129+ public static var shared : Self { . read }
130+
131+ /// Exclusive (alias for `write`)
132+ @_alwaysEmitIntoClient
133+ public static var exclusive : Self { . write }
134+
135+ /// Unlock (alias for `none`)
136+ @_alwaysEmitIntoClient
137+ public static var unlock : Self { . none }
126138 }
127139}
128140
@@ -131,7 +143,8 @@ extension FileDescriptor {
131143 ///
132144 /// If the open file description already has a lock, the old lock is
133145 /// 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.
146+ /// that is if the syscall would throw `.resourceTemporarilyUnavailable`
147+ /// (aka `EAGAIN`), this will return `false`.
135148 ///
136149 /// Open file description locks are associated with an open file
137150 /// description (see `FileDescriptor.open`). Duplicated
@@ -156,29 +169,31 @@ extension FileDescriptor {
156169 /// - retryOnInterrupt: Whether to retry the operation if it throws
157170 /// ``Errno/interrupted``. The default is `true`. Pass `false` to try
158171 /// only once and throw an error upon interruption.
172+ /// - Returns: `true` if the lock was aquired, `false` otherwise
159173 ///
160- /// The corresponding C function is `fcntl` with `F_OFD_SETLKW `.
174+ /// The corresponding C function is `fcntl` with `F_OFD_SETLK `.
161175 @_alwaysEmitIntoClient
162176 public func lock(
163177 _ kind: FileDescriptor . FileLock . Kind = . read,
164178 byteRange: ( some RangeExpression < Int64 > ) ? = Range ? . none,
165179 retryOnInterrupt: Bool = true
166- ) throws {
180+ ) throws -> Bool {
167181 let ( start, len) = _mapByteRangeToByteOffsets ( byteRange)
168- try _lock (
182+ return try _lock (
169183 kind,
170184 start: start,
171185 length: len,
186+ wait: false ,
187+ waitUntilTimeout: false ,
172188 retryOnInterrupt: retryOnInterrupt
173- ) . get ( )
189+ ) ? . get ( ) != nil
174190 }
175191
176- /// Try to set an advisory open file description lock.
192+ /// Set an advisory open file description lock.
177193 ///
178194 /// 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`.
195+ /// replaced. If the lock cannot be set because it is blocked by an existing lock and
196+ /// `wait` is true, this will wait until the lock can be set, otherwise returns `false`.
182197 ///
183198 /// Open file description locks are associated with an open file
184199 /// description (see `FileDescriptor.open`). Duplicated
@@ -200,38 +215,38 @@ extension FileDescriptor {
200215 /// - kind: The kind of lock to set
201216 /// - byteRange: The range of bytes over which to lock. Pass
202217 /// `nil` to consider the entire file.
218+ /// - wait: if `true` will wait until the lock can be set
203219 /// - retryOnInterrupt: Whether to retry the operation if it throws
204220 /// ``Errno/interrupted``. The default is `true`. Pass `false` to try
205221 /// only once and throw an error upon interruption.
206- /// - Returns: `true` if the lock was aquired, `false` otherwise
207222 ///
208- /// The corresponding C function is `fcntl` with `F_OFD_SETLK`.
223+ /// The corresponding C function is `fcntl` with `F_OFD_SETLK` or `F_OFD_SETLKW`.
224+ @discardableResult
209225 @_alwaysEmitIntoClient
210- public func tryLock (
226+ public func lock (
211227 _ kind: FileDescriptor . FileLock . Kind = . read,
212228 byteRange: ( some RangeExpression < Int64 > ) ? = Range ? . none,
229+ wait: Bool ,
213230 retryOnInterrupt: Bool = true
214231 ) throws -> Bool {
215232 let ( start, len) = _mapByteRangeToByteOffsets ( byteRange)
216- guard let _ = try _tryLock (
233+ return try _lock (
217234 kind,
218- waitUntilTimeout: false ,
219235 start: start,
220236 length: len,
237+ wait: wait,
238+ waitUntilTimeout: false ,
221239 retryOnInterrupt: retryOnInterrupt
222- ) ? . get ( ) else {
223- return false
224- }
225- return true
240+ ) ? . get ( ) != nil
226241 }
227242
228- #if !os(Linux)
229- /// Try to set an advisory open file description lock.
243+ #if !os(Linux)
244+ /// Set an advisory open file description lock.
230245 ///
231246 /// If the open file description already has a lock, the old lock is
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`.
247+ /// replaced. If the lock cannot be set because it is blocked by an existing lock and
248+ /// `waitUntilTimeout` is true, this will wait until the lock can be set (or the operating
249+ /// system's timeout expires ), otherwise returns `false`.
235250 ///
236251 /// Open file description locks are associated with an open file
237252 /// description (see `FileDescriptor.open`). Duplicated
@@ -253,33 +268,30 @@ extension FileDescriptor {
253268 /// - kind: The kind of lock to set
254269 /// - byteRange: The range of bytes over which to lock. Pass
255270 /// `nil` to consider the entire file.
256- /// - waitUntilTimeout: If `true`, will wait until a timeout (determined by the operating system)
271+ /// - waitUntilTimeout: if `true` will wait until the lock can be set or a timeout expires
257272 /// - retryOnInterrupt: Whether to retry the operation if it throws
258273 /// ``Errno/interrupted``. The default is `true`. Pass `false` to try
259274 /// only once and throw an error upon interruption.
260- /// - Returns: `true` if the lock was aquired, `false` otherwise
261275 ///
262- /// The corresponding C function is `fcntl` with `F_OFD_SETLK` or `F_OFD_SETLKWTIMEOUT` .
276+ /// The corresponding C function is `fcntl` with `F_OFD_SETLK` or `F_SETLKWTIMEOUT` .
263277 @_alwaysEmitIntoClient
264- public func tryLock (
278+ public func lock (
265279 _ kind: FileDescriptor . FileLock . Kind = . read,
266280 byteRange: ( some RangeExpression < Int64 > ) ? = Range ? . none,
267281 waitUntilTimeout: Bool ,
268282 retryOnInterrupt: Bool = true
269283 ) throws -> Bool {
270284 let ( start, len) = _mapByteRangeToByteOffsets ( byteRange)
271- guard let _ = try _tryLock (
285+ return try _lock (
272286 kind,
273- waitUntilTimeout: waitUntilTimeout,
274287 start: start,
275288 length: len,
289+ wait: false ,
290+ waitUntilTimeout: waitUntilTimeout,
276291 retryOnInterrupt: retryOnInterrupt
277- ) ? . get ( ) else {
278- return false
279- }
280- return true
292+ ) ? . get ( ) != nil
281293 }
282- #endif
294+ #endif
283295
284296 /// Remove an open file description lock.
285297 ///
@@ -311,49 +323,48 @@ extension FileDescriptor {
311323 @_alwaysEmitIntoClient
312324 public func unlock(
313325 byteRange: ( some RangeExpression < Int64 > ) ? = Range ? . none,
314- wait: Bool = false , // FIXME: needed?
315326 retryOnInterrupt: Bool = true
316327 ) throws {
317328 let ( start, len) = _mapByteRangeToByteOffsets ( byteRange)
318- guard let res = _tryLock (
329+ guard try _lock (
319330 . none,
320- waitUntilTimeout: false , // TODO: or we wait for timeout?
321331 start: start,
322332 length: len,
333+ wait: false ,
334+ waitUntilTimeout: false ,
323335 retryOnInterrupt: retryOnInterrupt
324- ) else {
325- preconditionFailure ( " TODO: Unlock should always succeed? " )
336+ ) ? . get ( ) != nil else {
337+ // NOTE: Errno and syscall composition wasn't designed for the modern
338+ // world. Releasing locks should always succeed and never be blocked
339+ // by an existing lock held elsewhere. But there's always a chance
340+ // that some effect (e.g. from NFS) causes `EGAIN` to be thrown for a
341+ // different reason/purpose. Here, in the very unlikely situation
342+ // that we somehow saw it, we convert the `nil` back to the error.
343+ throw Errno . resourceTemporarilyUnavailable
326344 }
327- return try res. get ( )
328345 }
329346
347+ /// Internal lock entry point, returns `nil` if blocked by existing lock.
348+ /// Both `wait` and `waitUntilTimeout` cannot both be true (passed as bools to avoid
349+ /// spurious enum in the ABI).
330350 @usableFromInline
331351 internal func _lock(
332352 _ kind: FileDescriptor . FileLock . Kind ,
333353 start: Int64 ,
334354 length: Int64 ,
335- retryOnInterrupt: Bool
336- ) -> Result < ( ) , Errno > {
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 ,
355+ wait: Bool ,
345356 waitUntilTimeout: Bool ,
346- start: Int64 ,
347- length: Int64 ,
348357 retryOnInterrupt: Bool
349358 ) -> Result < ( ) , Errno > ? {
359+ precondition ( !wait || !waitUntilTimeout)
360+ let cmd : FileDescriptor . Command
361+ if waitUntilTimeout {
350362#if os(Linux)
351- precondition ( !waitUntilTimeout , " `waitUntilTimeout` unavailable on Linux " )
363+ preconditionFailure ( " `waitUntilTimeout` unavailable on Linux " )
352364#endif
353-
354- let cmd : Control . Command
355- if waitUntilTimeout {
356365 cmd = . setOFDLockWaitTimout
366+ } else if wait {
367+ cmd = . setOFDLockWait
357368 } else {
358369 cmd = . setOFDLock
359370 }
@@ -363,5 +374,6 @@ extension FileDescriptor {
363374 _fcntl ( cmd, & lock, retryOnInterrupt: retryOnInterrupt) )
364375 }
365376}
366- #endif
377+
378+ #endif // !os(Windows)
367379
0 commit comments