Fix ioselectors_kqueue raising wrong exceptions (#24079)

kqueue will remove pipes automatically if their read end is closed.
Unfortunately this means that trying to unregister it (which is
necessary to clean up resources & for consistency with other ioselectors
implementations) will set an ENOENT error, which currently raises an
exception.

(ETA: in other words, it is currently impossible to call unregister on a
pipe fd without potentially getting the selector into an invalid state
on platforms with kqueue.)

Avoid this issue by ignoring ENOENT errors returned from kqueue.

(Tested on FreeBSD. I added a test case to the tioselectors file; the
seemingly unrelated change is to fix a race condition that doesn't
appear on Linux, so that it would run my code too.)
This commit is contained in:
bptato
2024-09-08 22:50:10 +02:00
committed by GitHub
parent ca28c256f3
commit 29a7d60acb
2 changed files with 26 additions and 3 deletions

View File

@@ -194,7 +194,9 @@ when hasThreadSupport:
if s.changesLength > 0:
if kevent(s.kqFD, addr(s.changes[0]), cint(s.changesLength),
nil, 0, nil) == -1:
raiseIOSelectorsError(osLastError())
let res = osLastError()
if cint(res) != ENOENT: # ignore pipes whose read end is closed
raiseIOSelectorsError(res)
s.changesLength = 0
else:
template modifyKQueue[T](s: Selector[T], nident: uint, nfilter: cshort,
@@ -211,7 +213,9 @@ else:
if length > 0:
if kevent(s.kqFD, addr(s.changes[0]), length,
nil, 0, nil) == -1:
raiseIOSelectorsError(osLastError())
let res = osLastError()
if cint(res) != ENOENT: # ignore pipes whose read end is closed
raiseIOSelectorsError(res)
s.changes.setLen(0)
proc registerHandle*[T](s: Selector[T], fd: int | SocketHandle,

View File

@@ -58,7 +58,11 @@ when not defined(windows):
registerHandle(selector, client_socket, {Event.Write}, 0)
freeAddrInfo(aiList)
discard selector.select(100)
# make sure both sockets are selected
var nevs = 0
while nevs < 2:
nevs += selector.select(100).len
var sockAddress: SockAddr
var addrLen = sizeof(sockAddress).Socklen
@@ -427,6 +431,20 @@ when not defined(windows):
doAssert(res[0].fd == dirfd and
{Event.Vnode, Event.VnodeDelete} <= res[0].events)
proc pipe_test(): bool =
# closing the read end of a pipe will result in it automatically
# being removed from the kqueue; make sure no exception is raised
var s = newSelector[int]()
var fds: array[2, cint]
discard pipe(fds)
s.registerHandle(fds[1], {Write}, 0)
discard close(fds[0])
let res = s.select(-1)
doAssert(res.len == 1)
s.unregister(fds[1])
discard close(fds[1])
return true
when hasThreadSupport:
var counter = 0
@@ -468,6 +486,7 @@ when not defined(windows):
when defined(macosx) or defined(freebsd) or defined(openbsd) or
defined(netbsd):
processTest("File notification test...", vnode_test())
processTest("Pipe test...", pipe_test())
echo("All tests passed!")
else:
import nativesockets, winlean, os, osproc