Ok, i have found why:
In that commit there is a new templated implementation for newSelector()
(0.17.2) to newSelector[T]() (0.17.3+) in linux specifically it is using epoll.
proc newSelector*[T](): Selector[T] =
# Retrieve the maximum fd count (for current OS) via getrlimit()
var a = RLimit()
if getrlimit(RLIMIT_NOFILE, a) != 0:
raiseOsError(osLastError())
var maxFD = int(a.rlim_max)
doAssert(maxFD > 0)
var epollFD = epoll_create(MAX_EPOLL_EVENTS)
if epollFD < 0:
raiseOsError(osLastError())
when hasThreadSupport:
result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T])))
result.epollFD = epollFD
result.maxFD = maxFD
result.fds = allocSharedArray[SelectorKey[T]](maxFD)
else:
result = Selector[T]()
result.epollFD = epollFD
result.maxFD = maxFD
result.fds = newSeq[SelectorKey[T]](maxFD)
As you see, it is allocating newSeq[SelectorKey[T]](maxFD)
in my system (docker alpine) maxFD is set to 1M and SelectorKey is defined as:
SelectorKey[T] = object
ident: int
events: set[Event]
param: int
data: T
sizeof SelectorKey[AsyncData] is 40 so a simple math 40*1M = 40M
A simple workaround that uses 10x less memory:
proc newSelector*[T](): Selector[T] =
# Retrieve the maximum fd count (for current OS) via getrlimit()
var a = RLimit()
if getrlimit(RLIMIT_NOFILE, a) != 0:
raiseOsError(osLastError())
var maxFD = int(a.rlim_max)
doAssert(maxFD > 0)
var epollFD = epoll_create(MAX_EPOLL_EVENTS)
if epollFD < 0:
raiseOsError(osLastError())
when defined(maxFileDescriptors):
const maxFileDescriptors {.intdefine.}: int = 132072
if maxFD > maxFileDescriptors:
maxFD = maxFileDescriptors
when hasThreadSupport:
result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T])))
result.epollFD = epollFD
result.maxFD = maxFD
result.fds = allocSharedArray[SelectorKey[T]](maxFD)
else:
result = Selector[T]()
result.epollFD = epollFD
result.maxFD = maxFD
result.fds = newSeq[SelectorKey[T]](maxFD)
For a proper solution we should dinamically increase fds if we are requesting
more than 132072 file descriptors