@@ -127,11 +127,11 @@ extension FileDescriptor.FileLock {
127
127
}
128
128
129
129
extension 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 .
135
135
///
136
136
/// Open file description locks are associated with an open file
137
137
/// description (see `FileDescriptor.open`). Duplicated
@@ -146,69 +146,92 @@ extension FileDescriptor {
146
146
/// Open file description locks are inherited by child processes across
147
147
/// `fork`, etc.
148
148
///
149
+ /// Passing a lock kind of `.none` will remove a lock (equivalent to calling
150
+ /// `FileDescriptor.unlock()`).
151
+ ///
149
152
/// - 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
151
155
/// `nil` to consider the entire file.
152
156
/// - retryOnInterrupt: Whether to retry the operation if it throws
153
157
/// ``Errno/interrupted``. The default is `true`. Pass `false` to try
154
158
/// only once and throw an error upon interruption.
155
- /// - Returns; `.none` if there are no locks, otherwise returns the
156
- /// strongest conflicting lock
157
159
///
158
- /// The corresponding C function is `fcntl` with `F_OFD_GETLK `.
160
+ /// The corresponding C function is `fcntl` with `F_OFD_SETLKW `.
159
161
@_alwaysEmitIntoClient
160
- public func getConflictingLock(
162
+ public func lock(
163
+ _ kind: FileDescriptor . FileLock . Kind = . read,
161
164
byteRange: ( some RangeExpression < Int64 > ) ? = Range ? . none,
162
165
retryOnInterrupt: Bool = true
163
- ) throws -> FileDescriptor . FileLock . Kind {
166
+ ) throws {
164
167
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
167
173
) . get ( )
168
174
}
169
175
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
196
224
}
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
202
226
}
203
227
204
- /// Set an open file description lock.
228
+ #if !os(Linux)
229
+ /// Try to set an advisory open file description lock.
205
230
///
206
231
/// 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`.
212
235
///
213
236
/// Open file description locks are associated with an open file
214
237
/// description (see `FileDescriptor.open`). Duplicated
@@ -230,29 +253,33 @@ extension FileDescriptor {
230
253
/// - kind: The kind of lock to set
231
254
/// - byteRange: The range of bytes over which to lock. Pass
232
255
/// `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)
234
257
/// - retryOnInterrupt: Whether to retry the operation if it throws
235
258
/// ``Errno/interrupted``. The default is `true`. Pass `false` to try
236
259
/// only once and throw an error upon interruption.
260
+ /// - Returns: `true` if the lock was aquired, `false` otherwise
237
261
///
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` .
240
263
@_alwaysEmitIntoClient
241
- public func lock (
264
+ public func tryLock (
242
265
_ kind: FileDescriptor . FileLock . Kind = . read,
243
266
byteRange: ( some RangeExpression < Int64 > ) ? = Range ? . none,
244
- wait : Bool = false ,
267
+ waitUntilTimeout : Bool ,
245
268
retryOnInterrupt: Bool = true
246
- ) throws {
269
+ ) throws -> Bool {
247
270
let ( start, len) = _mapByteRangeToByteOffsets ( byteRange)
248
- try _lock (
271
+ guard let _ = try _tryLock (
249
272
kind,
273
+ waitUntilTimeout: waitUntilTimeout,
250
274
start: start,
251
275
length: len,
252
- wait: wait,
253
276
retryOnInterrupt: retryOnInterrupt
254
- ) . get ( )
277
+ ) ? . get ( ) else {
278
+ return false
279
+ }
280
+ return true
255
281
}
282
+ #endif
256
283
257
284
/// Remove an open file description lock.
258
285
///
@@ -275,7 +302,6 @@ extension FileDescriptor {
275
302
/// - Parameters:
276
303
/// - byteRange: The range of bytes over which to lock. Pass
277
304
/// `nil` to consider the entire file.
278
- /// - wait: Whether to wait (block) until the request can be completed
279
305
/// - retryOnInterrupt: Whether to retry the operation if it throws
280
306
/// ``Errno/interrupted``. The default is `true`. Pass `false` to try
281
307
/// only once and throw an error upon interruption.
@@ -289,27 +315,52 @@ extension FileDescriptor {
289
315
retryOnInterrupt: Bool = true
290
316
) throws {
291
317
let ( start, len) = _mapByteRangeToByteOffsets ( byteRange)
292
- try _lock (
318
+ guard let res = _tryLock (
293
319
. none,
320
+ waitUntilTimeout: false , // TODO: or we wait for timeout?
294
321
start: start,
295
322
length: len,
296
- wait: wait,
297
323
retryOnInterrupt: retryOnInterrupt
298
- ) . get ( )
324
+ ) else {
325
+ preconditionFailure ( " TODO: Unlock should always succeed? " )
326
+ }
327
+ return try res. get ( )
299
328
}
300
329
301
330
@usableFromInline
302
331
internal func _lock(
303
332
_ kind: FileDescriptor . FileLock . Kind ,
304
333
start: Int64 ,
305
334
length: Int64 ,
306
- wait: Bool ,
307
335
retryOnInterrupt: Bool
308
336
) -> 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) )
313
364
}
314
365
}
315
366
#endif
0 commit comments