@@ -123,6 +123,18 @@ extension FileDescriptor.FileLock {
123
123
public static var none : Self {
124
124
Self ( rawValue: CInterop . CShort ( truncatingIfNeeded: F_UNLCK) )
125
125
}
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 }
126
138
}
127
139
}
128
140
@@ -131,7 +143,8 @@ extension FileDescriptor {
131
143
///
132
144
/// If the open file description already has a lock, the old lock is
133
145
/// 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`.
135
148
///
136
149
/// Open file description locks are associated with an open file
137
150
/// description (see `FileDescriptor.open`). Duplicated
@@ -156,29 +169,31 @@ extension FileDescriptor {
156
169
/// - retryOnInterrupt: Whether to retry the operation if it throws
157
170
/// ``Errno/interrupted``. The default is `true`. Pass `false` to try
158
171
/// only once and throw an error upon interruption.
172
+ /// - Returns: `true` if the lock was aquired, `false` otherwise
159
173
///
160
- /// The corresponding C function is `fcntl` with `F_OFD_SETLKW `.
174
+ /// The corresponding C function is `fcntl` with `F_OFD_SETLK `.
161
175
@_alwaysEmitIntoClient
162
176
public func lock(
163
177
_ kind: FileDescriptor . FileLock . Kind = . read,
164
178
byteRange: ( some RangeExpression < Int64 > ) ? = Range ? . none,
165
179
retryOnInterrupt: Bool = true
166
- ) throws {
180
+ ) throws -> Bool {
167
181
let ( start, len) = _mapByteRangeToByteOffsets ( byteRange)
168
- try _lock (
182
+ return try _lock (
169
183
kind,
170
184
start: start,
171
185
length: len,
186
+ wait: false ,
187
+ waitUntilTimeout: false ,
172
188
retryOnInterrupt: retryOnInterrupt
173
- ) . get ( )
189
+ ) ? . get ( ) != nil
174
190
}
175
191
176
- /// Try to set an advisory open file description lock.
192
+ /// Set an advisory open file description lock.
177
193
///
178
194
/// 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`.
182
197
///
183
198
/// Open file description locks are associated with an open file
184
199
/// description (see `FileDescriptor.open`). Duplicated
@@ -200,38 +215,38 @@ extension FileDescriptor {
200
215
/// - kind: The kind of lock to set
201
216
/// - byteRange: The range of bytes over which to lock. Pass
202
217
/// `nil` to consider the entire file.
218
+ /// - wait: if `true` will wait until the lock can be set
203
219
/// - retryOnInterrupt: Whether to retry the operation if it throws
204
220
/// ``Errno/interrupted``. The default is `true`. Pass `false` to try
205
221
/// only once and throw an error upon interruption.
206
- /// - Returns: `true` if the lock was aquired, `false` otherwise
207
222
///
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
209
225
@_alwaysEmitIntoClient
210
- public func tryLock (
226
+ public func lock (
211
227
_ kind: FileDescriptor . FileLock . Kind = . read,
212
228
byteRange: ( some RangeExpression < Int64 > ) ? = Range ? . none,
229
+ wait: Bool ,
213
230
retryOnInterrupt: Bool = true
214
231
) throws -> Bool {
215
232
let ( start, len) = _mapByteRangeToByteOffsets ( byteRange)
216
- guard let _ = try _tryLock (
233
+ return try _lock (
217
234
kind,
218
- waitUntilTimeout: false ,
219
235
start: start,
220
236
length: len,
237
+ wait: wait,
238
+ waitUntilTimeout: false ,
221
239
retryOnInterrupt: retryOnInterrupt
222
- ) ? . get ( ) else {
223
- return false
224
- }
225
- return true
240
+ ) ? . get ( ) != nil
226
241
}
227
242
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.
230
245
///
231
246
/// 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`.
235
250
///
236
251
/// Open file description locks are associated with an open file
237
252
/// description (see `FileDescriptor.open`). Duplicated
@@ -253,33 +268,30 @@ extension FileDescriptor {
253
268
/// - kind: The kind of lock to set
254
269
/// - byteRange: The range of bytes over which to lock. Pass
255
270
/// `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
257
272
/// - retryOnInterrupt: Whether to retry the operation if it throws
258
273
/// ``Errno/interrupted``. The default is `true`. Pass `false` to try
259
274
/// only once and throw an error upon interruption.
260
- /// - Returns: `true` if the lock was aquired, `false` otherwise
261
275
///
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` .
263
277
@_alwaysEmitIntoClient
264
- public func tryLock (
278
+ public func lock (
265
279
_ kind: FileDescriptor . FileLock . Kind = . read,
266
280
byteRange: ( some RangeExpression < Int64 > ) ? = Range ? . none,
267
281
waitUntilTimeout: Bool ,
268
282
retryOnInterrupt: Bool = true
269
283
) throws -> Bool {
270
284
let ( start, len) = _mapByteRangeToByteOffsets ( byteRange)
271
- guard let _ = try _tryLock (
285
+ return try _lock (
272
286
kind,
273
- waitUntilTimeout: waitUntilTimeout,
274
287
start: start,
275
288
length: len,
289
+ wait: false ,
290
+ waitUntilTimeout: waitUntilTimeout,
276
291
retryOnInterrupt: retryOnInterrupt
277
- ) ? . get ( ) else {
278
- return false
279
- }
280
- return true
292
+ ) ? . get ( ) != nil
281
293
}
282
- #endif
294
+ #endif
283
295
284
296
/// Remove an open file description lock.
285
297
///
@@ -311,49 +323,48 @@ extension FileDescriptor {
311
323
@_alwaysEmitIntoClient
312
324
public func unlock(
313
325
byteRange: ( some RangeExpression < Int64 > ) ? = Range ? . none,
314
- wait: Bool = false , // FIXME: needed?
315
326
retryOnInterrupt: Bool = true
316
327
) throws {
317
328
let ( start, len) = _mapByteRangeToByteOffsets ( byteRange)
318
- guard let res = _tryLock (
329
+ guard try _lock (
319
330
. none,
320
- waitUntilTimeout: false , // TODO: or we wait for timeout?
321
331
start: start,
322
332
length: len,
333
+ wait: false ,
334
+ waitUntilTimeout: false ,
323
335
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
326
344
}
327
- return try res. get ( )
328
345
}
329
346
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).
330
350
@usableFromInline
331
351
internal func _lock(
332
352
_ kind: FileDescriptor . FileLock . Kind ,
333
353
start: Int64 ,
334
354
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 ,
345
356
waitUntilTimeout: Bool ,
346
- start: Int64 ,
347
- length: Int64 ,
348
357
retryOnInterrupt: Bool
349
358
) -> Result < ( ) , Errno > ? {
359
+ precondition ( !wait || !waitUntilTimeout)
360
+ let cmd : FileDescriptor . Command
361
+ if waitUntilTimeout {
350
362
#if os(Linux)
351
- precondition ( !waitUntilTimeout , " `waitUntilTimeout` unavailable on Linux " )
363
+ preconditionFailure ( " `waitUntilTimeout` unavailable on Linux " )
352
364
#endif
353
-
354
- let cmd : Control . Command
355
- if waitUntilTimeout {
356
365
cmd = . setOFDLockWaitTimout
366
+ } else if wait {
367
+ cmd = . setOFDLockWait
357
368
} else {
358
369
cmd = . setOFDLock
359
370
}
@@ -363,5 +374,6 @@ extension FileDescriptor {
363
374
_fcntl ( cmd, & lock, retryOnInterrupt: retryOnInterrupt) )
364
375
}
365
376
}
366
- #endif
377
+
378
+ #endif // !os(Windows)
367
379
0 commit comments