Report queue overflows instead of dropping them
`IN_Q_OVERFLOW` arrives with watch descriptor -1, so the path lookup failed and the event was silently discarded. It is now delivered with an empty path so consumers can rescan after the kernel dropped events.
This commit is contained in:
@@ -118,6 +118,8 @@ Watch flags: `.dontFollow`, `.onlyDir`, `.oneShot`.
|
|||||||
|
|
||||||
Kernel-only flags returned in events: `.isDir`, `.ignored`, `.queueOverflow`, `.unmount`.
|
Kernel-only flags returned in events: `.isDir`, `.ignored`, `.queueOverflow`, `.unmount`.
|
||||||
|
|
||||||
|
When the kernel queue overflows, events are lost and a single event with `.queueOverflow` is delivered instead. It has no path and a watch descriptor of `-1`; rescan the watched directories if you must not miss changes.
|
||||||
|
|
||||||
## Removing a Watch
|
## Removing a Watch
|
||||||
|
|
||||||
Every `addWatch` variant returns one or more watch descriptors that you can use to remove the watch later:
|
Every `addWatch` variant returns one or more watch descriptors that you can use to remove the watch later:
|
||||||
|
|||||||
@@ -93,6 +93,9 @@ public actor Inotify {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func transform(_ rawEvent: RawInotifyEvent) async -> InotifyEvent? {
|
private func transform(_ rawEvent: RawInotifyEvent) async -> InotifyEvent? {
|
||||||
|
if rawEvent.mask.contains(.queueOverflow) {
|
||||||
|
return InotifyEvent(from: rawEvent, inDirectory: "")
|
||||||
|
}
|
||||||
guard let path = self.watches.path(forId: rawEvent.watchDescriptor) else { return nil }
|
guard let path = self.watches.path(forId: rawEvent.watchDescriptor) else { return nil }
|
||||||
guard !self.excludedItemNames.contains(rawEvent.name) else { return nil }
|
guard !self.excludedItemNames.contains(rawEvent.name) else { return nil }
|
||||||
let event = InotifyEvent.init(from: rawEvent, inDirectory: path)
|
let event = InotifyEvent.init(from: rawEvent, inDirectory: path)
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import SystemPackage
|
import SystemPackage
|
||||||
|
|
||||||
|
/// A filesystem event delivered by an ``Inotify`` instance.
|
||||||
|
///
|
||||||
|
/// When the kernel's event queue overflows, it drops events and reports a
|
||||||
|
/// single event whose ``mask`` contains ``InotifyEventMask/queueOverflow``.
|
||||||
|
/// Such an event belongs to no watch: its ``watchDescriptor`` is `-1` and
|
||||||
|
/// its ``path`` is empty. Consumers that must not miss changes should
|
||||||
|
/// rescan the watched trees when they receive one.
|
||||||
public struct InotifyEvent: Sendable, Hashable, CustomStringConvertible {
|
public struct InotifyEvent: Sendable, Hashable, CustomStringConvertible {
|
||||||
public let watchDescriptor: Int32
|
public let watchDescriptor: Int32
|
||||||
public let mask: InotifyEventMask
|
public let mask: InotifyEventMask
|
||||||
|
|||||||
@@ -40,4 +40,35 @@ struct InotifyLimitTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test func reportsQueueOverflowInsteadOfDroppingIt() async throws {
|
||||||
|
try await withTempDir { dir in
|
||||||
|
try await withInotifyWatchLimit(of: 1, for: [.queuedEvents]) {
|
||||||
|
let watcher = try Inotify()
|
||||||
|
try await watcher.addWatch(path: dir, mask: .allEvents)
|
||||||
|
let overflowTask = Task { () -> (InotifyEvent?, Int) in
|
||||||
|
var received = 0
|
||||||
|
for await event in await watcher.events {
|
||||||
|
received += 1
|
||||||
|
if event.mask.contains(.queueOverflow) { return (event, received) }
|
||||||
|
}
|
||||||
|
return (nil, received)
|
||||||
|
}
|
||||||
|
|
||||||
|
let deadline = ContinuousClock.now + .seconds(5)
|
||||||
|
var index = 0
|
||||||
|
while !overflowTask.isCancelled, ContinuousClock.now < deadline {
|
||||||
|
try createFile(at: "\(dir)/burst-\(index).txt", contents: "hello")
|
||||||
|
index += 1
|
||||||
|
if index % 200 == 0 { await Task.yield() }
|
||||||
|
}
|
||||||
|
overflowTask.cancel()
|
||||||
|
let (overflow, received) = await overflowTask.value
|
||||||
|
|
||||||
|
#expect(overflow != nil, "Expected a queue overflow event after \(index) file creations and \(received) received events")
|
||||||
|
#expect(overflow?.watchDescriptor == -1)
|
||||||
|
#expect(overflow?.path == "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user