Skip to content

[FD Leak] asyncdispatch: dispatcher's epoll/kqueue fd can never be closed and leaks (one fd per dispatcher, one per terminated thread) #26102

Description

@cryo2010

Nim Version

Nim Compiler Version 2.3.1 [MacOSX: arm64] (devel, also reproduces with 2.2.10)

Description

A PDispatcher's selector owns an OS resource: an epoll fd (Linux), a kqueue
fd plus a helper socket (macOS/BSD), or an IO completion port handle
(Windows). There is no way to release it:

  • asyncdispatch exposes no close/destroy for PDispatcher
    (std/selectors has a working close(), but it is unreachable through
    the asyncdispatch API)
  • no destructor/finalizer is attached, so a garbage-collected dispatcher
    leaks the fd
  • nothing closes the thread-local dispatcher when a thread terminates

Every dropped dispatcher therefore leaks at least one fd. The practical
worst case is thread pools: any thread that touches async (e.g. a
waitFor in a worker) permanently leaks one fd when it exits, so
long-running servers with short-lived worker threads exhaust the fd limit.

import std/[asyncdispatch, os]

proc openFds(): int =
  for _ in walkDir("/proc/self/fd"): inc result

echo "fds at start: ", openFds()
for i in 0..<10:
  let d = newDispatcher()
  setGlobalDispatcher(d)
  waitFor sleepAsync(1)   # force selector creation/use
  setGlobalDispatcher(nil)
  GC_fullCollect()
echo "fds after 10 dispatcher cycles: ", openFds()

Compiled and run on Linux with:

nim c --mm:orc -d:useMalloc --debugger:native displeak.nim
valgrind --track-fds=yes ./displeak

Output (program + valgrind fd tracking):

fds at start: 27          # baseline includes valgrind's own fds
fds after 10 dispatcher cycles: 37

==440== FILE DESCRIPTORS: 29 open (3 std) at exit.
==440== Open file descriptor 28:
==440==    at 0x4973487: epoll_create1 (syscall-template.S:120)
==440==    by 0x1200A6: selectors::newSelector (ioselectors_epoll.nim:86)
==440==    by 0x123ECC: asyncdispatch::newDispatcher (asyncdispatch.nim:1209)
==440==    by 0x127488: NimMainModule (displeak.nim:8)
==440==    by 0x127599: NimMainInner (system.nim:959)
==440==    by 0x1275AC: NimMain (system.nim:970)
==440==    by 0x1275CE: main (system.nim:978)
[... 9 more identical entries, fds 19-27, one per loop iteration ...]

Current Output


Expected Output


Known Workarounds

No response

Additional Information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions