# # # Nim's Runtime Library # (c) Copyright 2015 Dominik Picheta # # See the file "copying.txt", included in this # distribution, for details about the copyright. # # TODO: Docs. import os, hashes when defined(linux): import posix, epoll elif defined(macosx) or defined(freebsd) or defined(openbsd) or defined(netbsd): import posix, kqueue, times elif defined(windows): import winlean else: import posix const MultiThreaded = defined(useStdlibThreading) when MultiThreaded: import sharedtables type SelectorData = pointer else: import tables type SelectorData = RootRef proc hash*(x: SocketHandle): Hash {.borrow.} proc `$`*(x: SocketHandle): string {.borrow.} type Event* = enum EvRead, EvWrite, EvError SelectorKey* = object fd*: SocketHandle events*: set[Event] ## The events which ``fd`` listens for. data*: SelectorData ## User object. ReadyInfo* = tuple[key: SelectorKey, events: set[Event]] when defined(nimdoc): type Selector* = ref object ## An object which holds file descriptors to be checked for read/write ## status. proc register*(s: Selector, fd: SocketHandle, events: set[Event], data: SelectorData): SelectorKey {.discardable.} = ## Registers file descriptor ``fd`` to selector ``s`` with a set of Event ## ``events``. proc update*(s: Selector, fd: SocketHandle, events: set[Event]): SelectorKey {.discardable.} = ## Updates the events which ``fd`` wants notifications for. proc unregister*(s: Selector, fd: SocketHandle): SelectorKey {.discardable.} = ## Unregisters file descriptor ``fd`` from selector ``s``. proc close*(s: Selector) = ## Closes the selector proc select*(s: Selector, timeout: int): seq[ReadyInfo] = ## 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 ``ReadyInfo`` tuple which determines which events are ready ## on the ``fd``. proc newSelector*(): Selector = ## Creates a new selector proc contains*(s: Selector, fd: SocketHandle): bool = ## Determines whether selector contains a file descriptor. proc `[]`*(s: Selector, fd: SocketHandle): SelectorKey = ## Retrieves the selector key for ``fd``. elif defined(linux): type Selector* = object epollFD: cint events: array[64, epoll_event] when MultiThreaded: fds: SharedTable[SocketHandle, SelectorKey] else: fds: Table[SocketHandle, SelectorKey] proc createEventStruct(events: set[Event], fd: SocketHandle): 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: var Selector, fd: SocketHandle, events: set[Event], data: SelectorData) = var event = createEventStruct(events, fd) if events != {}: if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0: raiseOSError(osLastError()) s.fds[fd] = SelectorKey(fd: fd, events: events, data: data) proc update*(s: var Selector, fd: SocketHandle, events: set[Event]) = 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: raiseOSError(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: raiseOSError(osLastError()) else: if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fd, addr(event)) != 0: raiseOSError(osLastError()) s.fds[fd].events = events proc unregister*(s: var Selector, fd: SocketHandle) = if s.fds[fd].events != {}: 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? raiseOSError(err) s.fds.del(fd) proc close*(s: var Selector) = when MultiThreaded: deinitSharedTable(s.fds) if s.epollFD.close() != 0: raiseOSError(osLastError()) proc epollHasFd(s: Selector, fd: SocketHandle): 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 raiseOSError(err) proc select*(s: var Selector, timeout: int): seq[ReadyInfo] = result = @[] let evNum = epoll_wait(s.epollFD, addr s.events[0], 64.cint, timeout.cint) if evNum < 0: let err = osLastError() if err.cint == EINTR: return @[] raiseOSError(err) if evNum == 0: return @[] for i in 0 .. = 1000: Timespec(tv_sec: (timeout div 1000).Time, tv_nsec: 0) else: Timespec(tv_sec: 0.Time, tv_nsec: timeout * 1000000) let evNum = kevent(s.kqFD, nil, 0, addr s.events[0], 64.cint, addr tv) if evNum < 0: let err = osLastError() if err.cint == EINTR: return @[] raiseOSError(err) if evNum == 0: return @[] for i in 0 .. 0: echo ready[0].events i.inc if i == 6: selector.unregister(sock.getFD) selector.close() break