Cancel the event reader before closing the descriptor

`deinit` closed the inotify descriptor while its dispatch source was
still active. The kernel drops the epoll registration on close, but
libdispatch keeps its own; an instance created afterwards that reuses
the descriptor number could inherit that stale state and never
receive events. Roughly one test run in three lost a single event
this way.

The reader is now cancelled in `deinit` and the descriptor closed in
its cancel handler, as libdispatch requires.
This commit is contained in:
T. R. Bernstein
2026-09-13 22:59:15 +02:00
parent 39f3428bff
commit dcc08eb928
2 changed files with 31 additions and 2 deletions
+7 -2
View File
@@ -5,7 +5,7 @@ public actor Inotify {
private let fd: CInt
private var excludedItemNames: Set<String> = []
private var watches = InotifyWatchManager()
private var eventReader: any DispatchSourceRead
private nonisolated(unsafe) let eventReader: any DispatchSourceRead
private nonisolated let eventStream: AsyncStream<RawInotifyEvent>
public nonisolated var events: AsyncCompactMapSequence<AsyncStream<RawInotifyEvent>, InotifyEvent> {
self.eventStream.compactMap(self.transform(_:))
@@ -73,7 +73,11 @@ public actor Inotify {
}
deinit {
cinotify_deinit(self.fd)
// The file descriptor is closed by the reader's cancel handler once
// libdispatch has unregistered it. Closing it here would leave a
// registration behind that a later instance reusing the descriptor
// number could inherit, silently losing its events.
self.eventReader.cancel()
}
private func transform(_ rawEvent: RawInotifyEvent) async -> InotifyEvent? {
@@ -112,6 +116,7 @@ public actor Inotify {
}
}
reader.setCancelHandler {
cinotify_deinit(fd)
continuation.finish()
}
reader.activate()