# # # Nimrod's Runtime Library # (c) Copyright 2014 Dominik Picheta # # See the file "copying.txt", included in this # distribution, for details about the copyright. # # TODO: Docs. import tables, os, unsigned, hashes when defined(linux): import posix, epoll elif defined(windows): import winlean else: import posix proc hash*(x: TSocketHandle): THash {.borrow.} proc `$`*(x: TSocketHandle): string {.borrow.} type TEvent* = enum EvRead, EvWrite PSelectorKey* = ref object fd*: TSocketHandle events*: set[TEvent] ## The events which ``fd`` listens for. data*: PObject ## User object. TReadyInfo* = tuple[key: PSelectorKey, events: set[TEvent]] when defined(nimdoc): type PSelector* = ref object ## An object which holds file descripters to be checked for read/write ## status. fds: TTable[TSocketHandle, PSelectorKey] proc register*(s: PSelector, fd: TSocketHandle, events: set[TEvent], data: PObject): PSelectorKey {.discardable.} = ## Registers file descriptor ``fd`` to selector ``s`` with a set of TEvent ## ``events``. proc update*(s: PSelector, fd: TSocketHandle, events: set[TEvent]): PSelectorKey {.discardable.} = ## Updates the events which ``fd`` wants notifications for. proc select*(s: PSelector, timeout: int): seq[TReadyInfo] = ## The ``events`` field of the returned ``key`` contains the original events ## for which the ``fd`` was bound. This is contrary to the ``events`` field ## of the ``TReadyInfo`` tuple which determines which events are ready ## on the ``fd``. proc contains*(s: PSelector, fd: TSocketHandle): bool = ## Determines whether selector contains a file descriptor. proc `[]`*(s: PSelector, fd: TSocketHandle): PSelectorKey = ## Retrieves the selector key for ``fd``. elif defined(linux): type PSelector* = ref object epollFD: cint events: array[64, epoll_event] fds: TTable[TSocketHandle, PSelectorKey] proc createEventStruct(events: set[TEvent], fd: TSocketHandle): epoll_event = if EvRead in events: result.events = EPOLLIN if EvWrite in events: result.events = result.events or EPOLLOUT result.events = result.events or EPOLLRDHUP result.data.fd = fd.cint proc register*(s: PSelector, fd: TSocketHandle, events: set[TEvent], data: PObject): PSelectorKey {.discardable.} = ## Registers file descriptor ``fd`` to selector ``s`` with a set of TEvent ## ``events``. var event = createEventStruct(events, fd) if events != {}: if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0: OSError(OSLastError()) var key = PSelectorKey(fd: fd, events: events, data: data) s.fds[fd] = key result = key proc update*(s: PSelector, fd: TSocketHandle, events: set[TEvent]): PSelectorKey {.discardable.} = ## Updates the events which ``fd`` wants notifications for. if s.fds[fd].events != events: if events == {}: # This fd is idle -- it should not be registered to epoll. # But it should remain a part of this selector instance. # This is to prevent epoll_wait from returning immediately # because its got fds which are waiting for no events and # are therefore constantly ready. (leading to 100% CPU usage). if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fd, nil) != 0: OSError(OSLastError()) s.fds[fd].events = events else: var event = createEventStruct(events, fd) if s.fds[fd].events == {}: # This fd is idle. It's not a member of this epoll instance and must # be re-registered. if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0: OSError(OSLastError()) else: if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fd, addr(event)) != 0: OSError(OSLastError()) s.fds[fd].events = events result = s.fds[fd] proc unregister*(s: PSelector, fd: TSocketHandle): PSelectorKey {.discardable.} = if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fd, nil) != 0: let err = OSLastError() if err.cint notin {ENOENT, EBADF}: # TODO: Why do we sometimes get an EBADF? Is this normal? OSError(err) result = s.fds[fd] s.fds.del(fd) proc close*(s: PSelector) = if s.epollFD.close() != 0: OSError(OSLastError()) dealloc(addr s.events) # TODO: Test this proc epollHasFd(s: PSelector, fd: TSocketHandle): bool = result = true var event = createEventStruct(s.fds[fd].events, fd) if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fd, addr(event)) != 0: let err = osLastError() if err.cint in {ENOENT, EBADF}: return false OSError(OSLastError()) proc select*(s: PSelector, timeout: int): seq[TReadyInfo] = ## ## The ``events`` field of the returned ``key`` contains the original events ## for which the ``fd`` was bound. This is contrary to the ``events`` field ## of the ``TReadyInfo`` tuple which determines which events are ready ## on the ``fd``. result = @[] let evNum = epoll_wait(s.epollFD, addr s.events[0], 64.cint, timeout.cint) if evNum < 0: OSError(OSLastError()) if evNum == 0: return @[] for i in 0 .. 0: echo ready[0].events i.inc if i == 6: assert selector.unregister(sock.getFD).fd == sock.getFD selector.close() break