@@ -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  ///
@@ -315,45 +327,45 @@ extension FileDescriptor {
315327    retryOnInterrupt:  Bool  =  true 
316328  )  throws  { 
317329    let  ( start,  len)  =  _mapByteRangeToByteOffsets ( byteRange) 
318-     guard  let  res  =   _tryLock ( 
330+     guard  try   _lock ( 
319331      . none, 
320-       waitUntilTimeout:  false ,  // TODO: or we wait for timeout?
321332      start:  start, 
322333      length:  len, 
334+       wait:  false , 
335+       waitUntilTimeout:  false , 
323336      retryOnInterrupt:  retryOnInterrupt
324-     )  else  { 
325-       preconditionFailure ( " TODO: Unlock should always succeed? " ) 
337+     ) ? . get ( )  !=  nil  else  { 
338+       // NOTE: Errno and syscall composition wasn't designed for the modern
339+       // world. Releasing locks should always succeed and never be blocked
340+       // by an existing lock held elsewhere. But there's always a chance
341+       // that some effect (e.g. from NFS) causes `EGAIN` to be thrown for a
342+       // different reason/purpose. Here, in the very unlikely situation
343+       // that we somehow saw it, we convert the `nil` back to the error.
344+       throw  Errno . resourceTemporarilyUnavailable
326345    } 
327-     return  try . get ( ) 
328346  } 
329347
348+   /// Internal lock entry point, returns `nil` if blocked by existing lock.
349+   /// Both `wait` and `waitUntilTimeout` cannot both be true (passed as bools to avoid
350+   /// spurious enum in the ABI).
330351  @usableFromInline  
331352  internal  func  _lock( 
332353    _ kind:  FileDescriptor . FileLock . Kind , 
333354    start:  Int64 , 
334355    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 , 
356+     wait:  Bool , 
345357    waitUntilTimeout:  Bool , 
346-     start:  Int64 , 
347-     length:  Int64 , 
348358    retryOnInterrupt:  Bool 
349359  )  ->  Result < ( ) ,  Errno > ? { 
360+     precondition ( !wait || !waitUntilTimeout) 
361+     let  cmd :  FileDescriptor . Command 
362+     if  waitUntilTimeout { 
350363#if os(Linux) 
351-     precondition ( !waitUntilTimeout ,   " `waitUntilTimeout` unavailable on Linux " ) 
364+        preconditionFailure ( " `waitUntilTimeout` unavailable on Linux " ) 
352365#endif 
353- 
354-     let  cmd :  Control . Command 
355-     if  waitUntilTimeout { 
356366      cmd =  . setOFDLockWaitTimout
367+     }  else  if  wait { 
368+       cmd =  . setOFDLockWait
357369    }  else  { 
358370      cmd =  . setOFDLock
359371    } 
@@ -363,5 +375,6 @@ extension FileDescriptor {
363375      _fcntl ( cmd,  & lock,  retryOnInterrupt:  retryOnInterrupt) ) 
364376  } 
365377} 
366- #endif 
378+ 
379+ #endif // !os(Windows) 
367380
0 commit comments