nim-chronos/chronos/ioselects/ioselectors_epoll.nim

525 lines
18 KiB
Nim

#
#
# Nim's Runtime Library
# (c) Copyright 2016 Eugene Kabanov
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# This module implements Linux epoll().
{.push raises: [Defect].}
import posix, times, epoll
# Maximum number of events that can be returned
const MAX_EPOLL_EVENTS = 64
when not defined(android):
type
SignalFdInfo* {.importc: "struct signalfd_siginfo",
header: "<sys/signalfd.h>", pure, final.} = object
ssi_signo*: uint32
ssi_errno*: int32
ssi_code*: int32
ssi_pid*: uint32
ssi_uid*: uint32
ssi_fd*: int32
ssi_tid*: uint32
ssi_band*: uint32
ssi_overrun*: uint32
ssi_trapno*: uint32
ssi_status*: int32
ssi_int*: int32
ssi_ptr*: uint64
ssi_utime*: uint64
ssi_stime*: uint64
ssi_addr*: uint64
pad* {.importc: "__pad".}: array[0..47, uint8]
proc timerfd_create(clock_id: ClockId, flags: cint): cint
{.cdecl, importc: "timerfd_create", header: "<sys/timerfd.h>".}
proc timerfd_settime(ufd: cint, flags: cint,
utmr: var Itimerspec, otmr: var Itimerspec): cint
{.cdecl, importc: "timerfd_settime", header: "<sys/timerfd.h>".}
proc eventfd(count: cuint, flags: cint): cint
{.cdecl, importc: "eventfd", header: "<sys/eventfd.h>".}
when not defined(android):
proc signalfd(fd: cint, mask: var Sigset, flags: cint): cint
{.cdecl, importc: "signalfd", header: "<sys/signalfd.h>".}
when hasThreadSupport:
type
SelectorImpl[T] = object
epollFD: cint
numFD: int
fds: ptr SharedArray[SelectorKey[T]]
count: int
Selector*[T] = ptr SelectorImpl[T]
else:
type
SelectorImpl[T] = object
epollFD: cint
numFD: int
fds: seq[SelectorKey[T]]
count: int
Selector*[T] = ref SelectorImpl[T]
type
SelectEventImpl = object
efd: cint
SelectEvent* = ptr SelectEventImpl
proc newSelector*[T](): Selector[T] {.raises: [Defect, OSError].} =
# Retrieve the maximum fd count (for current OS) via getrlimit()
var a = RLimit()
# Start with a reasonable size, checkFd() will grow this on demand
const numFD = 1024
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.numFD = numFD
result.fds = allocSharedArray[SelectorKey[T]](numFD)
else:
result = Selector[T]()
result.epollFD = epollFD
result.numFD = numFD
result.fds = newSeq[SelectorKey[T]](numFD)
for i in 0 ..< numFD:
result.fds[i].ident = InvalidIdent
proc close*[T](s: Selector[T]) =
let res = posix.close(s.epollFD)
when hasThreadSupport:
deallocSharedArray(s.fds)
deallocShared(cast[pointer](s))
if res != 0:
raiseIOSelectorsError(osLastError())
proc newSelectEvent*(): SelectEvent {.raises: [Defect, OSError, IOSelectorsException].} =
let fdci = eventfd(0, 0)
if fdci == -1:
raiseIOSelectorsError(osLastError())
setNonBlocking(fdci)
result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl)))
result.efd = fdci
proc trigger*(ev: SelectEvent) {.raises: [Defect, IOSelectorsException].} =
var data: uint64 = 1
if posix.write(ev.efd, addr data, sizeof(uint64)) == -1:
raiseIOSelectorsError(osLastError())
proc close*(ev: SelectEvent) {.raises: [Defect, IOSelectorsException].} =
let res = posix.close(ev.efd)
deallocShared(cast[pointer](ev))
if res != 0:
raiseIOSelectorsError(osLastError())
template checkFd(s, f) =
if f >= s.numFD:
var numFD = s.numFD
while numFD <= f: numFD *= 2
when hasThreadSupport:
s.fds = reallocSharedArray(s.fds, numFD)
else:
s.fds.setLen(numFD)
for i in s.numFD ..< numFD:
s.fds[i].ident = InvalidIdent
s.numFD = numFD
proc registerHandle*[T](s: Selector[T], fd: int | SocketHandle,
events: set[Event], data: T) {.
raises: [Defect, IOSelectorsException].} =
let fdi = int(fd)
s.checkFd(fdi)
doAssert(s.fds[fdi].ident == InvalidIdent, "Descriptor " & $fdi & " already registered")
s.setKey(fdi, events, 0, data)
if events != {}:
var epv = EpollEvent(events: EPOLLRDHUP)
epv.data.u64 = fdi.uint
if Event.Read in events: epv.events = epv.events or EPOLLIN
if Event.Write in events: epv.events = epv.events or EPOLLOUT
if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) != 0:
raiseIOSelectorsError(osLastError())
inc(s.count)
proc updateHandle*[T](s: Selector[T], fd: int | SocketHandle, events: set[Event]) {.
raises: [Defect, IOSelectorsException].} =
let maskEvents = {Event.Timer, Event.Signal, Event.Process, Event.Vnode,
Event.User, Event.Oneshot, Event.Error}
let fdi = int(fd)
s.checkFd(fdi)
var pkey = addr(s.fds[fdi])
doAssert(pkey.ident != InvalidIdent,
"Descriptor " & $fdi & " is not registered in the selector!")
doAssert(pkey.events * maskEvents == {})
if pkey.events != events:
var epv = EpollEvent(events: EPOLLRDHUP)
epv.data.u64 = fdi.uint
if Event.Read in events: epv.events = epv.events or EPOLLIN
if Event.Write in events: epv.events = epv.events or EPOLLOUT
if pkey.events == {}:
if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) != 0:
raiseIOSelectorsError(osLastError())
inc(s.count)
else:
if events != {}:
if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fdi.cint, addr epv) != 0:
raiseIOSelectorsError(osLastError())
else:
if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) != 0:
raiseIOSelectorsError(osLastError())
dec(s.count)
pkey.events = events
proc unregister*[T](s: Selector[T], fd: int|SocketHandle) {.raises: [Defect, IOSelectorsException].} =
let fdi = int(fd)
s.checkFd(fdi)
var pkey = addr(s.fds[fdi])
doAssert(pkey.ident != InvalidIdent,
"Descriptor " & $fdi & " is not registered in the selector!")
if pkey.events != {}:
when not defined(android):
if Event.Read in pkey.events or Event.Write in pkey.events or Event.User in pkey.events:
var epv = EpollEvent()
# TODO: Refactor all these EPOLL_CTL_DEL + dec(s.count) into a proc.
if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) != 0:
raiseIOSelectorsError(osLastError())
dec(s.count)
elif Event.Timer in pkey.events:
if Event.Finished notin pkey.events:
var epv = EpollEvent()
if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) != 0:
raiseIOSelectorsError(osLastError())
dec(s.count)
if posix.close(cint(fdi)) != 0:
raiseIOSelectorsError(osLastError())
elif Event.Signal in pkey.events:
var epv = EpollEvent()
if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) != 0:
raiseIOSelectorsError(osLastError())
var nmask, omask: Sigset
discard sigemptyset(nmask)
discard sigemptyset(omask)
discard sigaddset(nmask, cint(s.fds[fdi].param))
unblockSignals(nmask, omask)
dec(s.count)
if posix.close(cint(fdi)) != 0:
raiseIOSelectorsError(osLastError())
elif Event.Process in pkey.events:
if Event.Finished notin pkey.events:
var epv = EpollEvent()
if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) != 0:
raiseIOSelectorsError(osLastError())
var nmask, omask: Sigset
discard sigemptyset(nmask)
discard sigemptyset(omask)
discard sigaddset(nmask, SIGCHLD)
unblockSignals(nmask, omask)
dec(s.count)
if posix.close(cint(fdi)) != 0:
raiseIOSelectorsError(osLastError())
else:
if Event.Read in pkey.events or Event.Write in pkey.events or Event.User in pkey.events:
var epv = EpollEvent()
if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) != 0:
raiseIOSelectorsError(osLastError())
dec(s.count)
elif Event.Timer in pkey.events:
if Event.Finished notin pkey.events:
var epv = EpollEvent()
if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) != 0:
raiseIOSelectorsError(osLastError())
dec(s.count)
if posix.close(cint(fdi)) != 0:
raiseIOSelectorsError(osLastError())
clearKey(pkey)
proc unregister*[T](s: Selector[T], ev: SelectEvent) {.
raises: [Defect, IOSelectorsException].} =
let fdi = int(ev.efd)
s.checkFd(fdi)
var pkey = addr(s.fds[fdi])
doAssert(pkey.ident != InvalidIdent, "Event is not registered in the queue!")
doAssert(Event.User in pkey.events)
var epv = EpollEvent()
if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) != 0:
raiseIOSelectorsError(osLastError())
dec(s.count)
clearKey(pkey)
proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool,
data: T): int {.
discardable, raises: [Defect, IOSelectorsException].} =
var
newTs: Itimerspec
oldTs: Itimerspec
let fdi = timerfd_create(CLOCK_MONOTONIC, 0).int
if fdi == -1:
raiseIOSelectorsError(osLastError())
setNonBlocking(fdi.cint)
s.checkFd(fdi)
doAssert(s.fds[fdi].ident == InvalidIdent)
var events = {Event.Timer}
var epv = EpollEvent(events: EPOLLIN or EPOLLRDHUP)
epv.data.u64 = fdi.uint
if oneshot:
newTs.it_interval.tv_sec = posix.Time(0)
newTs.it_interval.tv_nsec = 0
newTs.it_value.tv_sec = posix.Time(timeout div 1_000)
newTs.it_value.tv_nsec = (timeout %% 1_000) * 1_000_000
incl(events, Event.Oneshot)
epv.events = epv.events or EPOLLONESHOT
else:
newTs.it_interval.tv_sec = posix.Time(timeout div 1000)
newTs.it_interval.tv_nsec = (timeout %% 1_000) * 1_000_000
newTs.it_value.tv_sec = newTs.it_interval.tv_sec
newTs.it_value.tv_nsec = newTs.it_interval.tv_nsec
if timerfd_settime(fdi.cint, cint(0), newTs, oldTs) != 0:
raiseIOSelectorsError(osLastError())
if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) != 0:
raiseIOSelectorsError(osLastError())
s.setKey(fdi, events, 0, data)
inc(s.count)
result = fdi
when not defined(android):
proc registerSignal*[T](s: Selector[T], signal: int,
data: T): int {.
discardable, raises: [Defect, OSError, IOSelectorsException].} =
var
nmask: Sigset
omask: Sigset
discard sigemptyset(nmask)
discard sigemptyset(omask)
discard sigaddset(nmask, cint(signal))
blockSignals(nmask, omask)
let fdi = signalfd(-1, nmask, 0).int
if fdi == -1:
raiseIOSelectorsError(osLastError())
setNonBlocking(fdi.cint)
s.checkFd(fdi)
doAssert(s.fds[fdi].ident == InvalidIdent)
var epv = EpollEvent(events: EPOLLIN or EPOLLRDHUP)
epv.data.u64 = fdi.uint
if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) != 0:
raiseIOSelectorsError(osLastError())
s.setKey(fdi, {Event.Signal}, signal, data)
inc(s.count)
result = fdi
proc registerProcess*[T](s: Selector, pid: int,
data: T): int {.
discardable, raises: [Defect, IOSelectorsException].} =
var
nmask: Sigset
omask: Sigset
discard sigemptyset(nmask)
discard sigemptyset(omask)
discard sigaddset(nmask, posix.SIGCHLD)
blockSignals(nmask, omask)
let fdi = signalfd(-1, nmask, 0).int
if fdi == -1:
raiseIOSelectorsError(osLastError())
setNonBlocking(fdi.cint)
s.checkFd(fdi)
doAssert(s.fds[fdi].ident == InvalidIdent)
var epv = EpollEvent(events: EPOLLIN or EPOLLRDHUP)
epv.data.u64 = fdi.uint
epv.events = EPOLLIN or EPOLLRDHUP
if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) != 0:
raiseIOSelectorsError(osLastError())
s.setKey(fdi, {Event.Process, Event.Oneshot}, pid, data)
inc(s.count)
result = fdi
proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
let fdi = int(ev.efd)
doAssert(s.fds[fdi].ident == InvalidIdent, "Event is already registered in the queue!")
s.setKey(fdi, {Event.User}, 0, data)
var epv = EpollEvent(events: EPOLLIN or EPOLLRDHUP)
epv.data.u64 = ev.efd.uint
if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, ev.efd, addr epv) != 0:
raiseIOSelectorsError(osLastError())
inc(s.count)
proc selectInto*[T](s: Selector[T], timeout: int,
results: var openArray[ReadyKey]): int {.raises: [Defect, IOSelectorsException].} =
var
resTable: array[MAX_EPOLL_EVENTS, EpollEvent]
maxres = MAX_EPOLL_EVENTS
i, k: int
if maxres > len(results):
maxres = len(results)
verifySelectParams(timeout)
let count = epoll_wait(s.epollFD, addr(resTable[0]), maxres.cint,
timeout.cint)
if count < 0:
result = 0
let err = osLastError()
if cint(err) != EINTR:
raiseIOSelectorsError(err)
elif count == 0:
result = 0
else:
i = 0
k = 0
while i < count:
let fdi = int(resTable[i].data.u64)
let pevents = resTable[i].events
var pkey = addr(s.fds[fdi])
doAssert(pkey.ident != InvalidIdent)
var rkey = ReadyKey(fd: fdi, events: {})
if (pevents and EPOLLERR) != 0 or (pevents and EPOLLHUP) != 0:
if (pevents and EPOLLHUP) != 0:
rkey.errorCode = OSErrorCode ECONNRESET
else:
# Try reading SO_ERROR from fd.
var error: cint
var size = SockLen sizeof(error)
if getsockopt(SocketHandle fdi, SOL_SOCKET, SO_ERROR, addr(error),
addr(size)) == 0'i32:
rkey.errorCode = OSErrorCode error
rkey.events.incl(Event.Error)
if (pevents and EPOLLOUT) != 0:
rkey.events.incl(Event.Write)
when not defined(android):
if (pevents and EPOLLIN) != 0:
if Event.Read in pkey.events:
rkey.events.incl(Event.Read)
elif Event.Timer in pkey.events:
var data: uint64 = 0
if posix.read(cint(fdi), addr data,
sizeof(uint64)) != sizeof(uint64):
raiseIOSelectorsError(osLastError())
rkey.events.incl(Event.Timer)
elif Event.Signal in pkey.events:
var data = SignalFdInfo()
if posix.read(cint(fdi), addr data,
sizeof(SignalFdInfo)) != sizeof(SignalFdInfo):
raiseIOSelectorsError(osLastError())
rkey.events.incl(Event.Signal)
elif Event.Process in pkey.events:
var data = SignalFdInfo()
if posix.read(cint(fdi), addr data,
sizeof(SignalFdInfo)) != sizeof(SignalFdInfo):
raiseIOSelectorsError(osLastError())
if cast[int](data.ssi_pid) == pkey.param:
rkey.events.incl(Event.Process)
else:
inc(i)
continue
elif Event.User in pkey.events:
var data: uint64 = 0
if posix.read(cint(fdi), addr data,
sizeof(uint64)) != sizeof(uint64):
let err = osLastError()
if err == OSErrorCode(EAGAIN):
inc(i)
continue
else:
raiseIOSelectorsError(err)
rkey.events.incl(Event.User)
else:
if (pevents and EPOLLIN) != 0:
if Event.Read in pkey.events:
rkey.events.incl(Event.Read)
elif Event.Timer in pkey.events:
var data: uint64 = 0
if posix.read(cint(fdi), addr data,
sizeof(uint64)) != sizeof(uint64):
raiseIOSelectorsError(osLastError())
rkey.events.incl(Event.Timer)
elif Event.User in pkey.events:
var data: uint64 = 0
if posix.read(cint(fdi), addr data,
sizeof(uint64)) != sizeof(uint64):
let err = osLastError()
if err == OSErrorCode(EAGAIN):
inc(i)
continue
else:
raiseIOSelectorsError(err)
rkey.events.incl(Event.User)
if Event.Oneshot in pkey.events:
var epv = EpollEvent()
if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, cint(fdi), addr epv) != 0:
raiseIOSelectorsError(osLastError())
# we will not clear key until it will be unregistered, so
# application can obtain data, but we will decrease counter,
# because epoll is empty.
dec(s.count)
# we are marking key with `Finished` event, to avoid double decrease.
pkey.events.incl(Event.Finished)
results[k] = rkey
inc(k)
inc(i)
result = k
proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey] =
result = newSeq[ReadyKey](MAX_EPOLL_EVENTS)
let count = selectInto(s, timeout, result)
result.setLen(count)
template isEmpty*[T](s: Selector[T]): bool =
(s.count == 0)
proc contains*[T](s: Selector[T], fd: SocketHandle|int): bool {.inline.} =
let fdi = int(fd)
fdi < s.numFD and s.fds[fdi].ident != InvalidIdent
proc setData*[T](s: Selector[T], fd: SocketHandle|int, data: T): bool =
let fdi = int(fd)
s.checkFd(fdi)
if fdi in s:
s.fds[fdi].data = data
result = true
template withData*[T](s: Selector[T], fd: SocketHandle|int, value,
body: untyped) =
mixin checkFd
let fdi = int(fd)
if fdi in s:
var value = addr(s.fds[fdi].data)
body
template withData*[T](s: Selector[T], fd: SocketHandle|int, value, body1,
body2: untyped) =
let fdi = int(fd)
if fdi in s:
var value = addr(s.fds[fdi].data)
body1
else:
body2
proc getFd*[T](s: Selector[T]): int =
return s.epollFd.int