summary refs log tree commit diff stats
path: root/lib/pure
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure')
-rw-r--r--lib/pure/ioselectors.nim1764
-rw-r--r--lib/pure/terminal.nim26
2 files changed, 1779 insertions, 11 deletions
diff --git a/lib/pure/ioselectors.nim b/lib/pure/ioselectors.nim
new file mode 100644
index 000000000..034b182ab
--- /dev/null
+++ b/lib/pure/ioselectors.nim
@@ -0,0 +1,1764 @@
+#
+#
+#            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 allows high-level and efficient I/O multiplexing.
+##
+## Supported OS primitives: ``epoll``, ``kqueue``, ``poll`` and
+## Windows ``select``.
+##
+## To use threadsafe version of this module, it needs to be compiled
+## with both ``-d:threadsafe`` and ``--threads:on`` options.
+##
+## Supported features: files, sockets, pipes, timers, processes, signals
+## and user events.
+##
+## Fully supported OS: MacOSX, FreeBSD, OpenBSD, NetBSD, Linux.
+##
+## Partially supported OS: Windows (only sockets and user events),
+## Solaris (files, sockets, handles and user events).
+##
+## TODO: ``/dev/poll``, ``event ports`` and filesystem events.
+
+import os
+
+const hasThreadSupport = compileOption("threads") and defined(threadsafe)
+
+const supportedPlatform = defined(macosx) or defined(freebsd) or
+                          defined(netbsd) or defined(openbsd) or
+                          defined(linux)
+
+const bsdPlatform = defined(macosx) or defined(freebsd) or
+                    defined(netbsd) or defined(openbsd)
+
+when defined(linux):
+  import posix, times
+elif bsdPlatform:
+  import posix, kqueue, times
+elif defined(windows):
+  import winlean
+else:
+  import posix
+
+when defined(nimdoc):
+  type
+    Selector*[T] = ref object
+      ## An object which holds descriptors to be checked for read/write status
+
+    Event* {.pure.} = enum
+      ## An enum which hold event types
+      Read,    ## Descriptor is available for read
+      Write,   ## Descriptor is available for write
+      Timer,   ## Timer descriptor is completed
+      Signal,  ## Signal is raised
+      Process, ## Process is finished
+      Vnode,   ## Currently not supported
+      User,    ## User event is raised
+      Error    ## Error happens while waiting, for descriptor
+
+    ReadyKey*[T] = object
+      ## An object which holds result for descriptor
+      fd* : int ## file/socket descriptor
+      events*: set[Event] ## set of events
+      data*: T ## application-defined data
+
+    SelectEvent* = object
+      ## An object which holds user defined event
+
+  proc newSelector*[T](): Selector[T] =
+    ## Creates a new selector
+
+  proc close*[T](s: Selector[T]) =
+    ## Closes selector
+
+  proc registerHandle*[T](s: Selector[T], fd: SocketHandle, events: set[Event],
+                          data: T) =
+    ## Registers file/socket descriptor ``fd`` to selector ``s``
+    ## with events set in ``events``. The ``data`` is application-defined
+    ## data, which to be passed when event happens.
+
+  proc updateHandle*[T](s: Selector[T], fd: SocketHandle, events: set[Event]) =
+    ## Update file/socket descriptor ``fd``, registered in selector
+    ## ``s`` with new events set ``event``.
+
+  proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool,
+                         data: T): int {.discardable.} =
+    ## Registers timer notification with ``timeout`` in milliseconds
+    ## to selector ``s``.
+    ## If ``oneshot`` is ``true`` timer will be notified only once.
+    ## Set ``oneshot`` to ``false`` if your want periodic notifications.
+    ## The ``data`` is application-defined data, which to be passed, when
+    ## time limit expired.
+
+  proc registerSignal*[T](s: Selector[T], signal: int,
+                          data: T): int {.discardable.} =
+    ## Registers Unix signal notification with ``signal`` to selector
+    ## ``s``. The ``data`` is application-defined data, which to be
+    ## passed, when signal raises.
+    ##
+    ## This function is not supported for ``Windows``.
+
+  proc registerProcess*[T](s: Selector[T], pid: int,
+                           data: T): int {.discardable.} =
+    ## Registers process id (pid) notification when process has
+    ## exited to selector ``s``.
+    ## The ``data`` is application-defined data, which to be passed, when
+    ## process with ``pid`` has exited.
+
+  proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
+    ## Registers selector event ``ev`` to selector ``s``.
+    ## ``data`` application-defined data, which to be passed, when
+    ## ``ev`` happens.
+
+  proc newEvent*(): SelectEvent =
+    ## Creates new event ``SelectEvent``.
+
+  proc setEvent*(ev: SelectEvent) =
+    ## Trigger event ``ev``.
+
+  proc close*(ev: SelectEvent) =
+    ## Closes selector event ``ev``.
+
+  proc unregister*[T](s: Selector[T], ev: SelectEvent) =
+    ## Unregisters event ``ev`` from selector ``s``.
+
+  proc unregister*[T](s: Selector[T], fd: int|SocketHandle|cint) =
+    ## Unregisters file/socket descriptor ``fd`` from selector ``s``.
+
+  proc flush*[T](s: Selector[T]) =
+    ## Flushes all changes was made to kernel pool/queue.
+    ## This function is usefull only for BSD and MacOS, because
+    ## kqueue supports bulk changes to be made.
+    ## On Linux/Windows and other Posix compatible operation systems,
+    ## ``flush`` is alias for `discard`.
+
+  proc selectInto*[T](s: Selector[T], timeout: int,
+                      results: var openarray[ReadyKey[T]]): int =
+    ## Process call waiting for events registered in selector ``s``.
+    ## The ``timeout`` argument specifies the minimum number of milliseconds
+    ## the function will be blocked, if no events are not ready. Specifying a
+    ## timeout of ``-1`` causes function to block indefinitely.
+    ## All available events will be stored in ``results`` array.
+    ##
+    ## Function returns number of triggered events.
+
+  proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] =
+    ## Process call waiting for events registered in selector ``s``.
+    ## The ``timeout`` argument specifies the minimum number of milliseconds
+    ## the function will be blocked, if no events are not ready. Specifying a
+    ## timeout of -1 causes function to block indefinitely.
+    ##
+    ## Function returns sequence of triggered events.
+
+  template isEmpty*[T](s: Selector[T]): bool =
+    ## Returns ``true``, if there no registered events or descriptors
+    ## in selector.
+
+  template withData*[T](s: Selector[T], fd: SocketHandle, value,
+                        body: untyped) =
+    ## retrieves the application-data assigned with descriptor ``fd``
+    ## to ``value``. This ``value`` can be modified in the scope of
+    ## the ``withData`` call.
+    ##
+    ## .. code-block:: nim
+    ##
+    ##   s.withData(fd, value) do:
+    ##     # block is executed only if ``fd`` registered in selector ``s``
+    ##     value.uid = 1000
+    ##
+
+  template withData*[T](s: Selector[T], fd: SocketHandle, value,
+                        body1, body2: untyped) =
+    ## retrieves the application-data assigned with descriptor ``fd``
+    ## to ``value``. This ``value`` can be modified in the scope of
+    ## the ``withData`` call.
+    ##
+    ## .. code-block:: nim
+    ##
+    ##   s.withData(fd, value) do:
+    ##     # block is executed only if ``fd`` registered in selector ``s``.
+    ##     value.uid = 1000
+    ##   do:
+    ##     # block is executed if ``fd`` not registered in selector ``s``.
+    ##     raise
+    ##
+
+else:
+  when defined(macosx) or defined(freebsd):
+    when defined(macosx):
+      const maxDescriptors = 29 # KERN_MAXFILESPERPROC (MacOS)
+    else:
+      const maxDescriptors = 27 # KERN_MAXFILESPERPROC (FreeBSD)
+    proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr int,
+                newp: pointer, newplen: int): cint
+         {.importc: "sysctl",header: """#include <sys/types.h>
+                                        #include <sys/sysctl.h>"""}
+  elif defined(netbsd) or defined(openbsd):
+    # OpenBSD and NetBSD don't have KERN_MAXFILESPERPROC, so we are using
+    # KERN_MAXFILES, because KERN_MAXFILES is always bigger,
+    # than KERN_MAXFILESPERPROC
+    const maxDescriptors = 7 # KERN_MAXFILES
+    proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr int,
+                newp: pointer, newplen: int): cint
+         {.importc: "sysctl",header: """#include <sys/param.h>
+                                        #include <sys/sysctl.h>"""}
+  elif defined(linux) or defined(solaris):
+    proc ulimit(cmd: cint): clong
+         {.importc: "ulimit", header: "<ulimit.h>", varargs.}
+  elif defined(windows):
+    discard
+  else:
+    var
+      RLIMIT_NOFILE {.importc: "RLIMIT_NOFILE",
+                      header: "<sys/resource.h>".}: cint
+    type
+      rlimit {.importc: "struct rlimit",
+               header: "<sys/resource.h>", pure, final.} = object
+        rlim_cur: int
+        rlim_max: int
+    proc getrlimit(resource: cint, rlp: var rlimit): cint
+        {.importc: "getrlimit",header: "<sys/resource.h>".}
+
+  proc getMaxFds*(): int =
+    when defined(macosx) or defined(freebsd) or defined(netbsd) or
+         defined(openbsd):
+      var count = cint(0)
+      var size = sizeof(count)
+      var namearr = [cint(1), cint(maxDescriptors)]
+
+      if sysctl(addr namearr[0], 2, cast[pointer](addr count), addr size,
+                nil, 0) != 0:
+        raiseOsError(osLastError())
+      result = count
+    elif defined(linux) or defined(solaris):
+      result = int(ulimit(4, 0))
+    elif defined(windows):
+      result = FD_SETSIZE
+    else:
+      var a = rlimit()
+      if getrlimit(RLIMIT_NOFILE, a) != 0:
+        raiseOsError(osLastError())
+      result = a.rlim_max
+
+  when hasThreadSupport:
+    import locks
+
+  type
+    Event* {.pure.} = enum
+      Read, Write, Timer, Signal, Process, Vnode, User, Error,
+      flagHandle, flagTimer, flagSignal, flagProcess, flagVnode, flagUser,
+      flagOneshot
+
+    ReadyKey*[T] = object
+      fd* : int
+      events*: set[Event]
+      data*: T
+
+    SelectorKey[T] = object
+      ident : int
+      flags : set[Event]
+      param : int
+      key : ReadyKey[T]
+
+  when not defined(windows):
+    type
+      SharedArrayHolder[T] = object
+        part: array[16, T]
+      SharedArray {.unchecked.}[T] = array[0..100_000_000, T]
+
+    proc allocSharedArray[T](nsize: int): ptr SharedArray[T] =
+      let holder = cast[ptr SharedArrayHolder[T]](
+                     allocShared0(sizeof(T) * nsize)
+                   )
+      result = cast[ptr SharedArray[T]](addr(holder.part[0]))
+
+    proc deallocSharedArray[T](sa: ptr SharedArray[T]) =
+      deallocShared(cast[pointer](sa))
+
+    template setNonBlocking(fd) =
+      var x: int = fcntl(fd, F_GETFL, 0)
+      if x == -1: raiseOSError(osLastError())
+      else:
+        var mode = x or O_NONBLOCK
+        if fcntl(fd, F_SETFL, mode) == -1:
+          raiseOSError(osLastError())
+
+    template setKey(s, f1, f2, e, p, d) =
+      s.fds[f1].ident = f1
+      s.fds[f1].flags = e
+      s.fds[f1].param = p
+      s.fds[f1].key.fd = f2
+      s.fds[f1].key.data = d
+
+    template clearKey(s, f) =
+      s.fds[f].ident = 0
+      s.fds[f].flags = {}
+
+    template checkMaxFd(s, fd) =
+      if fd.uint >= s.maxFD:
+        raise newException(ValueError, "Maximum file descriptors exceeded")
+
+  when supportedPlatform:
+    template blockSignals(newmask: var Sigset, oldmask: var Sigset) =
+      when hasThreadSupport:
+        if posix.pthread_sigmask(SIG_BLOCK, newmask, oldmask) == -1:
+          raiseOSError(osLastError())
+      else:
+        if posix.sigprocmask(SIG_BLOCK, newmask, oldmask) == -1:
+          raiseOSError(osLastError())
+
+    template unblockSignals(newmask: var Sigset, oldmask: var Sigset) =
+      when hasThreadSupport:
+        if posix.pthread_sigmask(SIG_UNBLOCK, newmask, oldmask) == -1:
+          raiseOSError(osLastError())
+      else:
+        if posix.sigprocmask(SIG_UNBLOCK, newmask, oldmask) == -1:
+          raiseOSError(osLastError())
+  #
+  # BSD kqueue
+  #
+  # I have tried to adopt kqueue's EVFILT_USER filter for user-events, but it
+  # looks not very usable, because of 2 cases:
+  # 1) EVFILT_USER does not supported by OpenBSD and NetBSD
+  # 2) You can't have one event, which you can use with many kqueue handles.
+  # So decision was made in favor of the pipes
+  #
+  when bsdPlatform:
+    const
+      # Maximum number of cached changes
+      MAX_KQUEUE_CHANGE_EVENTS = 64
+      # Maximum number of events that can be returned
+      MAX_KQUEUE_RESULT_EVENTS = 64
+
+    type
+      SelectorImpl[T] = object
+        kqFD : cint
+        maxFD : uint
+        changesTable: array[MAX_KQUEUE_CHANGE_EVENTS, KEvent]
+        changesCount: int
+        fds: ptr SharedArray[SelectorKey[T]]
+        count: int
+        when hasThreadSupport:
+          changesLock: Lock
+      Selector*[T] = ptr SelectorImpl[T]
+
+    type
+      SelectEventImpl = object
+        rfd: cint
+        wfd: cint
+    # SelectEvent is declared as `ptr` to be placed in `shared memory`,
+    # so you can share one SelectEvent handle between threads.
+    type SelectEvent* = ptr SelectEventImpl
+
+    proc newSelector*[T](): Selector[T] =
+      var maxFD = getMaxFds()
+      var kqFD = kqueue()
+      if kqFD < 0:
+        raiseOsError(osLastError())
+
+      result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T])))
+      result.kqFD = kqFD
+      result.maxFD = maxFD.uint
+      result.fds = allocSharedArray[SelectorKey[T]](maxFD)
+      when hasThreadSupport:
+        initLock(result.changesLock)
+
+    proc close*[T](s: Selector[T]) =
+      if posix.close(s.kqFD) != 0:
+        raiseOSError(osLastError())
+      when hasThreadSupport:
+        deinitLock(s.changesLock)
+      deallocSharedArray(s.fds)
+      deallocShared(cast[pointer](s))
+
+    when hasThreadSupport:
+      template withChangeLock[T](s: Selector[T], body: untyped) =
+        acquire(s.changesLock)
+        {.locks: [s.changesLock].}:
+          try:
+            body
+          finally:
+            release(s.changesLock)
+    else:
+      template withChangeLock(s, body: untyped) =
+        body
+
+    template modifyKQueue[T](s: Selector[T], nident: uint, nfilter: cshort,
+                             nflags: cushort, nfflags: cuint, ndata: int,
+                             nudata: pointer) =
+      mixin withChangeLock
+      s.withChangeLock():
+        s.changesTable[s.changesCount] = KEvent(ident: nident,
+                                                filter: nfilter, flags: nflags,
+                                                fflags: nfflags, data: ndata,
+                                                udata: nudata)
+        inc(s.changesCount)
+        if s.changesCount == MAX_KQUEUE_CHANGE_EVENTS:
+          if kevent(s.kqFD, addr(s.changesTable[0]), cint(s.changesCount),
+                    nil, 0, nil) == -1:
+            raiseOSError(osLastError())
+          s.changesCount = 0
+
+    proc registerHandle*[T](s: Selector[T], fd: SocketHandle,
+                            events: set[Event], data: T) =
+      var fdi = int(fd)
+      s.checkMaxFd(fdi)
+      doAssert(s.fds[fdi].ident == 0)
+      setKey(s, fdi, fdi, {Event.flagHandle} + events, 0, data)
+      if events != {}:
+        if Event.Read in events:
+          modifyKQueue(s, fdi.uint, EVFILT_READ, EV_ADD, 0, 0, nil)
+          inc(s.count)
+        if Event.Write in events:
+          modifyKQueue(s, fdi.uint, EVFILT_WRITE, EV_ADD, 0, 0, nil)
+          inc(s.count)
+
+    proc updateHandle*[T](s: Selector[T], fd: SocketHandle,
+                          events: set[Event]) =
+      var fdi = int(fd)
+      s.checkMaxFd(fdi)
+      doAssert(s.fds[fdi].ident != 0)
+      doAssert(Event.flagHandle in s.fds[fdi].flags)
+      var ne = events + {Event.flagHandle}
+      var oe = s.fds[fdi].flags
+      if oe != ne:
+        if (Event.Read in oe) and (Event.Read notin ne):
+          modifyKQueue(s, fdi.uint, EVFILT_READ, EV_DELETE, 0, 0, nil)
+          dec(s.count)
+        if (Event.Write in oe) and (Event.Write notin ne):
+           modifyKQueue(s, fdi.uint, EVFILT_WRITE, EV_DELETE, 0, 0, nil)
+           dec(s.count)
+        if (Event.Read notin oe) and (Event.Read in ne):
+           modifyKQueue(s, fdi.uint, EVFILT_READ, EV_ADD, 0, 0, nil)
+           inc(s.count)
+        if (Event.Write notin oe) and (Event.Write in ne):
+           modifyKQueue(s, fdi.uint, EVFILT_WRITE, EV_ADD, 0, 0, nil)
+           inc(s.count)
+        s.fds[fdi].flags = ne
+
+    proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool,
+                           data: T): int {.discardable.} =
+      var fdi = posix.socket(posix.AF_INET, posix.SOCK_STREAM,
+                             posix.IPPROTO_TCP).int
+      if fdi == -1:
+        raiseOsError(osLastError())
+      s.checkMaxFd(fdi)
+      doAssert(s.fds[fdi].ident == 0)
+      var mflags = if oneshot: {Event.flagTimer, Event.flagOneshot}
+                   else: {Event.flagTimer}
+      var kflags: cushort = if oneshot: EV_ONESHOT or EV_ADD
+                            else: EV_ADD
+      setKey(s, fdi, fdi, mflags, 0, data)
+      # EVFILT_TIMER on Open/Net(BSD) has granularity of only milliseconds,
+      # but MacOS and FreeBSD allow use `0` as `fflags` to use milliseconds
+      # too
+      modifyKQueue(s, fdi.uint, EVFILT_TIMER, kflags, 0, cint(timeout), nil)
+      inc(s.count)
+      result = fdi
+
+    proc registerSignal*[T](s: Selector[T], signal: int,
+                            data: T): int {.discardable.} =
+      var fdi = posix.socket(posix.AF_INET, posix.SOCK_STREAM,
+                             posix.IPPROTO_TCP).int
+      if fdi == -1:
+        raiseOsError(osLastError())
+
+      s.checkMaxFd(fdi)
+      doAssert(s.fds[fdi].ident == 0)
+      setKey(s, fdi, signal, {Event.flagSignal}, signal, data)
+      # block signal `signal`
+      var nmask: Sigset
+      var omask: Sigset
+      discard sigemptyset(nmask)
+      discard sigemptyset(omask)
+      discard sigaddset(nmask, cint(signal))
+      blockSignals(nmask, omask)
+      # to be compatible with linux semantic we need to "eat" signals
+      posix.signal(cint(signal), SIG_IGN)
+      modifyKQueue(s, signal.uint, EVFILT_SIGNAL, EV_ADD, 0, 0,
+                   cast[pointer](fdi))
+      inc(s.count)
+      result = fdi
+
+    proc registerProcess*[T](s: Selector[T], pid: int,
+                             data: T): int {.discardable.} =
+      var fdi = posix.socket(posix.AF_INET, posix.SOCK_STREAM,
+                             posix.IPPROTO_TCP).int
+      if fdi == -1:
+        raiseOsError(osLastError())
+
+      s.checkMaxFd(fdi)
+      doAssert(s.fds[fdi].ident == 0)
+      var kflags: cushort = EV_ONESHOT or EV_ADD
+      setKey(s, fdi, pid, {Event.flagProcess, Event.flagOneshot}, pid, data)
+      modifyKQueue(s, pid.uint, EVFILT_PROC, kflags, NOTE_EXIT, 0,
+                   cast[pointer](fdi))
+      inc(s.count)
+      result = fdi
+
+    proc unregister*[T](s: Selector[T], fd: int|SocketHandle|cint) =
+      var fdi = int(fd)
+      if fdi.uint < s.maxFD:
+        var flags = s.fds[fdi].flags
+        var filter: cshort = 0
+        if s.fds[fdi].ident != 0 and flags != {}:
+          if Event.flagHandle in flags:
+            # if events == 0, than descriptor was modified with
+            # updateHandle(fd, 0), so it was already deleted from kqueue.
+            if flags != {Event.flagHandle}:
+              if Event.Read in flags:
+                modifyKQueue(s, fdi.uint, EVFILT_READ, EV_DELETE, 0, 0, nil)
+                dec(s.count)
+              if Event.Write in flags:
+                modifyKQueue(s, fdi.uint, EVFILT_WRITE, EV_DELETE, 0, 0, nil)
+                dec(s.count)
+          elif Event.flagTimer in flags:
+            filter = EVFILT_TIMER
+            discard posix.close(cint(s.fds[fdi].key.fd))
+            modifyKQueue(s, fdi.uint, filter, EV_DELETE, 0, 0, nil)
+            dec(s.count)
+          elif Event.flagSignal in flags:
+            filter = EVFILT_SIGNAL
+            # unblocking signal
+            var nmask = Sigset()
+            var omask = Sigset()
+            var signal = cint(s.fds[fdi].param)
+            discard sigaddset(nmask, signal)
+            unblockSignals(nmask, omask)
+            posix.signal(signal, SIG_DFL)
+            discard posix.close(cint(s.fds[fdi].key.fd))
+            modifyKQueue(s, fdi.uint, filter, EV_DELETE, 0, 0, nil)
+            dec(s.count)
+          elif Event.flagProcess in flags:
+            filter = EVFILT_PROC
+            discard posix.close(cint(s.fds[fdi].key.fd))
+            modifyKQueue(s, fdi.uint, filter, EV_DELETE, 0, 0, nil)
+            dec(s.count)
+          elif Event.flagUser in flags:
+            filter = EVFILT_READ
+            modifyKQueue(s, fdi.uint, filter, EV_DELETE, 0, 0, nil)
+            dec(s.count)
+          clearKey(s, fdi)
+
+    proc flush*[T](s: Selector[T]) =
+      s.withChangeLock():
+        var tv = Timespec()
+        if kevent(s.kqFD, addr(s.changesTable[0]), cint(s.changesCount),
+                  nil, 0, addr tv) == -1:
+          raiseOSError(osLastError())
+        s.changesCount = 0
+
+    template isEmpty*[T](s: Selector[T]): bool =
+      (s.count == 0)
+
+    proc newEvent*(): SelectEvent =
+      var fds: array[2, cint]
+
+      if posix.pipe(fds) == -1:
+        raiseOSError(osLastError())
+
+      setNonBlocking(fds[0])
+      setNonBlocking(fds[1])
+
+      result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl)))
+      result.rfd = fds[0]
+      result.wfd = fds[1]
+
+    proc setEvent*(ev: SelectEvent) =
+      var data: int = 1
+      if posix.write(ev.wfd, addr data, sizeof(int)) != sizeof(int):
+        raiseOSError(osLastError())
+
+    proc close*(ev: SelectEvent) =
+      discard posix.close(cint(ev.rfd))
+      discard posix.close(cint(ev.wfd))
+      deallocShared(cast[pointer](ev))
+
+    proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
+      let fdi = ev.rfd.int
+      doAssert(s.fds[fdi].ident == 0)
+      setKey(s, fdi, fdi, {Event.flagUser}, 0, data)
+      modifyKQueue(s, fdi.uint, EVFILT_READ, EV_ADD, 0, 0, nil)
+      inc(s.count)
+
+    proc unregister*[T](s: Selector[T], ev: SelectEvent) =
+      let fdi = ev.rfd.int
+      var flags = s.fds[fdi].flags
+      if s.fds[fdi].ident != 0 and flags != {}:
+        modifyKQueue(s, fdi.uint, EVFILT_READ, EV_DELETE, 0, 0, nil)
+        dec(s.count)
+        clearKey(s, fdi)
+
+    proc selectInto*[T](s: Selector[T], timeout: int,
+                        results: var openarray[ReadyKey[T]]): int =
+      var
+        tv: Timespec
+        resultsTable: array[MAX_KQUEUE_RESULT_EVENTS, KEvent]
+        ptv: ptr Timespec = addr tv
+
+      if timeout != -1:
+        if timeout >= 1000:
+          tv.tv_sec = (timeout div 1_000).Time
+          tv.tv_nsec = (timeout %% 1_000) * 1_000_000
+        else:
+          tv.tv_sec = 0.Time
+          tv.tv_nsec = timeout * 1_000_000
+      else:
+        ptv = nil
+
+      var maxResults = MAX_KQUEUE_RESULT_EVENTS
+      if maxResults > len(results):
+        maxResults = len(results)
+
+      var count = 0
+      s.withChangeLock():
+        count = kevent(s.kqFD,
+                       addr(s.changesTable[0]), cint(s.changesCount),
+                       addr(resultsTable[0]), cint(maxResults), ptv)
+        s.changesCount = 0
+      if count >= 0:
+        var skey: ptr SelectorKey[T]
+        var i = 0
+        var k = 0
+        while i < count:
+          var kevent = addr(resultsTable[i])
+          if (kevent.flags and EV_ERROR) == 0:
+            var events: set[Event] = {}
+            case kevent.filter
+            of EVFILT_READ:
+              skey = addr(s.fds[kevent.ident.int])
+              if Event.flagHandle in skey.flags:
+                events = {Event.Read}
+              elif Event.flagUser in skey.flags:
+                var data: int = 0
+                if posix.read(kevent.ident.cint, addr data,
+                              sizeof(int)) != sizeof(int):
+                  let err = osLastError()
+                  if err == OSErrorCode(EAGAIN):
+                    # someone already consumed event data
+                    inc(i)
+                    continue
+                  else:
+                    raiseOSError(osLastError())
+                  events = {Event.User}
+              else:
+                events = {Event.Read}
+            of EVFILT_WRITE:
+              skey = addr(s.fds[kevent.ident.int])
+              events = {Event.Write}
+            of EVFILT_TIMER:
+              skey = addr(s.fds[kevent.ident.int])
+              if Event.flagOneshot in skey.flags:
+                if posix.close(skey.ident.cint) == -1:
+                  raiseOSError(osLastError())
+                clearKey(s, skey.ident)
+                # no need to modify kqueue, because EV_ONESHOT is already made
+                # this for us
+                dec(s.count)
+              events = {Event.Timer}
+            of EVFILT_VNODE:
+              skey = addr(s.fds[kevent.ident.int])
+              events = {Event.Vnode}
+            of EVFILT_SIGNAL:
+              skey = addr(s.fds[cast[int](kevent.udata)])
+              events = {Event.Signal}
+            of EVFILT_PROC:
+              skey = addr(s.fds[cast[int](kevent.udata)])
+              if posix.close(skey.ident.cint) == -1:
+                raiseOSError(osLastError())
+              clearKey(s, skey.ident)
+              # no need to modify kqueue, because EV_ONESHOT is already made
+              # this for us
+              dec(s.count)
+              events = {Event.Process}
+            else:
+              raise newException(ValueError,
+                                 "Unsupported kqueue filter in queue")
+
+            if (kevent.flags and EV_EOF) != 0:
+              events = events + {Event.Error}
+            results[k].fd = skey.key.fd
+            results[k].events = events
+            results[k].data = skey.key.data
+            inc(k)
+          inc(i)
+        result = k
+      else:
+        result = 0
+        let err = osLastError()
+        if cint(err) != EINTR:
+          raiseOSError(err)
+
+    proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] =
+      result = newSeq[ReadyKey[T]](MAX_KQUEUE_RESULT_EVENTS)
+      var count = selectInto(s, timeout, result)
+      result.setLen(count)
+
+  #
+  # Linux epoll
+  #
+
+  elif defined(linux):
+    const
+      # Maximum number of events that can be returned
+      MAX_EPOLL_RESULT_EVENTS = 64
+    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]
+    type
+      eventFdData {.importc: "eventfd_t",
+                     header: "<sys/eventfd.h>", pure, final.} = uint64
+      epoll_data {.importc: "union epoll_data",
+                    header: "<sys/epoll.h>",
+                    pure, final.} = object
+        u64 {.importc: "u64".}: uint64
+
+      epoll_event {.importc: "struct epoll_event",
+                     header: "<sys/epoll.h>", pure, final.} = object
+        events: uint32 # Epoll events
+        data: epoll_data # User data variable
+    const
+      EPOLL_CTL_ADD = 1          # Add a file descriptor to the interface.
+      EPOLL_CTL_DEL = 2          # Remove a file descriptor from the interface.
+      EPOLL_CTL_MOD = 3          # Change file descriptor epoll_event structure.
+    const
+      EPOLLIN      = 0x00000001
+      EPOLLOUT     = 0x00000004
+      EPOLLERR     = 0x00000008
+      EPOLLHUP     = 0x00000010
+      EPOLLRDHUP   = 0x00002000
+      EPOLLONESHOT = 1 shl 30
+
+    proc epoll_create(size: cint): cint
+         {.importc: "epoll_create", header: "<sys/epoll.h>".}
+    proc epoll_ctl(epfd: cint; op: cint; fd: cint; event: ptr epoll_event): cint
+         {.importc: "epoll_ctl", header: "<sys/epoll.h>".}
+    proc epoll_wait(epfd: cint; events: ptr epoll_event; maxevents: cint;
+                     timeout: cint): cint
+         {.importc: "epoll_wait", header: "<sys/epoll.h>".}
+    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 signalfd(fd: cint, mask: var Sigset, flags: cint): cint
+         {.cdecl, importc: "signalfd", header: "<sys/signalfd.h>".}
+    proc eventfd(count: cuint, flags: cint): cint
+         {.cdecl, importc: "eventfd", header: "<sys/eventfd.h>".}
+
+    type
+      SelectorImpl[T] = object
+        epollFD : cint
+        maxFD : uint
+        fds: ptr SharedArray[SelectorKey[T]]
+        count: int
+
+      Selector*[T] = ptr SelectorImpl[T]
+
+      SelectEventImpl = object
+        efd: cint
+
+      SelectEvent* = ptr SelectEventImpl
+
+    proc newSelector*[T](): Selector[T] =
+      var maxFD = getMaxFds()
+      var epollFD = epoll_create(MAX_EPOLL_RESULT_EVENTS)
+      if epollFD < 0:
+        raiseOsError(osLastError())
+
+      result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T])))
+      result.epollFD = epollFD
+      result.maxFD = maxFD.uint
+      result.fds = allocSharedArray[SelectorKey[T]](maxFD)
+
+    proc close*[T](s: Selector[T]) =
+      if posix.close(s.epollFD) != 0:
+        raiseOSError(osLastError())
+      deallocSharedArray(s.fds)
+      deallocShared(cast[pointer](s))
+
+    proc registerHandle*[T](s: Selector[T], fd: SocketHandle,
+                            events: set[Event], data: T) =
+      var fdi = int(fd)
+      s.checkMaxFd(fdi)
+      doAssert(s.fds[fdi].ident == 0)
+      setKey(s, fdi, fdi, events + {Event.flagHandle}, 0, data)
+      if events != {}:
+        var epv: epoll_event
+        epv.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) == -1:
+          raiseOSError(osLastError())
+        inc(s.count)
+
+    proc updateHandle*[T](s: Selector[T], fd: SocketHandle,
+                          events: set[Event]) =
+      var fdi = int(fd)
+      s.checkMaxFd(fdi)
+      var oe = s.fds[fdi].flags
+      doAssert(s.fds[fdi].ident != 0)
+      doAssert(Event.flagHandle in oe)
+      var ne = events + {Event.flagHandle}
+      if oe != ne:
+        var epv: epoll_event
+        epv.data.u64 = fdi.uint
+        epv.events = EPOLLRDHUP
+
+        if Event.Read in events:
+          epv.events = epv.events or EPOLLIN
+        if Event.Write in events:
+          epv.events = epv.events or EPOLLOUT
+
+        if oe == {Event.flagHandle}:
+          if ne != {Event.flagHandle}:
+            if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint,
+                         addr epv) == -1:
+              raiseOSError(osLastError())
+            inc(s.count)
+        else:
+          if ne != {Event.flagHandle}:
+            if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fdi.cint,
+                         addr epv) == -1:
+              raiseOSError(osLastError())
+          else:
+            if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint,
+                         addr epv) == -1:
+              raiseOSError(osLastError())
+            dec(s.count)
+        s.fds[fdi].flags = ne
+
+    proc unregister*[T](s: Selector[T], fd: int|SocketHandle|cint) =
+      var epv: epoll_event
+      var fdi = int(fd)
+      if fdi.uint < s.maxFD:
+        var flags = s.fds[fdi].flags
+        if s.fds[fdi].ident != 0 and flags != {}:
+          if Event.flagHandle in flags:
+            # if events == {flagHandle}, then descriptor was already
+            # unregistered from epoll with updateHandle() call.
+            # This check is done to omit EBADF error.
+            if flags != {Event.flagHandle}:
+              if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint,
+                           addr epv) == -1:
+                raiseOSError(osLastError())
+              dec(s.count)
+          elif Event.flagTimer in flags:
+            if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
+              raiseOSError(osLastError())
+            discard posix.close(fdi.cint)
+            dec(s.count)
+          elif Event.flagSignal in flags:
+            if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
+              raiseOSError(osLastError())
+            var nmask: Sigset
+            var omask: Sigset
+            discard sigemptyset(nmask)
+            discard sigemptyset(omask)
+            discard sigaddset(nmask, cint(s.fds[fdi].param))
+            unblockSignals(nmask, omask)
+            discard posix.close(fdi.cint)
+            dec(s.count)
+          elif Event.flagProcess in flags:
+            if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
+              raiseOSError(osLastError())
+            var nmask: Sigset
+            var omask: Sigset
+            discard sigemptyset(nmask)
+            discard sigemptyset(omask)
+            discard sigaddset(nmask, SIGCHLD)
+            unblockSignals(nmask, omask)
+            discard posix.close(fdi.cint)
+            dec(s.count)
+          clearKey(s, fdi)
+
+    proc unregister*[T](s: Selector[T], ev: SelectEvent) =
+      let fdi = int(ev.efd)
+      if fdi.uint < s.maxFD:
+        if s.fds[fdi].ident != 0 and (Event.flagUser in s.fds[fdi].flags):
+          clearKey(s, fdi)
+          var epv: epoll_event
+          if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
+            raiseOSError(osLastError())
+          dec(s.count)
+
+    proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool,
+                           data: T): int {.discardable.} =
+      var
+        new_ts: Itimerspec
+        old_ts: Itimerspec
+      var fdi = timerfd_create(CLOCK_MONOTONIC, 0)
+      if fdi == -1:
+        raiseOSError(osLastError())
+      s.checkMaxFd(fdi)
+      doAssert(s.fds[fdi].ident == 0)
+      var flags = {Event.flagTimer}
+      var epv: epoll_event
+      epv.data.u64 = fdi.uint
+      epv.events = EPOLLIN or EPOLLRDHUP
+      setNonBlocking(fdi.cint)
+      if oneshot:
+        new_ts.it_interval.tv_sec = 0.Time
+        new_ts.it_interval.tv_nsec = 0
+        new_ts.it_value.tv_sec = (timeout div 1_000).Time
+        new_ts.it_value.tv_nsec = (timeout %% 1_000) * 1_000_000
+        flags = flags + {Event.flagOneshot}
+        epv.events = epv.events or EPOLLONESHOT
+      else:
+        new_ts.it_interval.tv_sec = (timeout div 1000).Time
+        new_ts.it_interval.tv_nsec = (timeout %% 1_000) * 1_000_000
+        new_ts.it_value.tv_sec = new_ts.it_interval.tv_sec
+        new_ts.it_value.tv_nsec = new_ts.it_interval.tv_nsec
+      if timerfd_settime(fdi.cint, cint(0), new_ts, old_ts) == -1:
+        raiseOSError(osLastError())
+      if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) == -1:
+        raiseOSError(osLastError())
+      setKey(s, fdi, fdi, flags, 0, data)
+      inc(s.count)
+      result = fdi
+
+    proc registerSignal*[T](s: Selector[T], signal: int,
+                            data: T): int {.discardable.} =
+      var
+        nmask: Sigset
+        omask: Sigset
+
+      discard sigemptyset(nmask)
+      discard sigemptyset(omask)
+      discard sigaddset(nmask, cint(signal))
+      blockSignals(nmask, omask)
+
+      var fdi = signalfd(-1, nmask, 0).int
+      if fdi == -1:
+        raiseOSError(osLastError())
+
+      s.checkMaxFd(fdi)
+      doAssert(s.fds[fdi].ident == 0)
+      setNonBlocking(fdi.cint)
+
+      var epv: epoll_event
+      epv.data.u64 = fdi.uint
+      epv.events = EPOLLIN or EPOLLRDHUP
+      if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) == -1:
+        raiseOSError(osLastError())
+      setKey(s, fdi, signal, {Event.flagSignal}, signal, data)
+      inc(s.count)
+      result = fdi
+
+    proc registerProcess*[T](s: Selector, pid: int,
+                             data: T): int {.discardable.} =
+      var
+        nmask: Sigset
+        omask: Sigset
+
+      discard sigemptyset(nmask)
+      discard sigemptyset(omask)
+      discard sigaddset(nmask, posix.SIGCHLD)
+      blockSignals(nmask, omask)
+
+      var fdi = signalfd(-1, nmask, 0).int
+      if fdi == -1:
+        raiseOSError(osLastError())
+
+      s.checkMaxFd(fdi)
+      doAssert(s.fds[fdi].ident == 0)
+      setNonBlocking(fdi.cint)
+
+      var epv: epoll_event
+      epv.data.u64 = fdi.uint
+      epv.events = EPOLLIN or EPOLLRDHUP
+      if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) == -1:
+        raiseOSError(osLastError())
+      setKey(s, fdi, pid, {Event.flagProcess}, pid, data)
+      inc(s.count)
+      result = fdi
+
+    proc flush*[T](s: Selector[T]) =
+      discard
+
+    template isEmpty*[T](s: Selector[T]): bool =
+      (s.count == 0)
+
+    proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
+      let fdi = int(ev.efd)
+      doAssert(s.fds[fdi].ident == 0)
+      setKey(s, fdi, fdi, {Event.flagUser}, 0, data)
+      var epv = epoll_event(events: EPOLLIN or EPOLLRDHUP)
+      epv.data.u64 = ev.efd.uint
+      if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, ev.efd, addr epv) == -1:
+        raiseOSError(osLastError())
+      inc(s.count)
+
+    proc setEvent*(ev: SelectEvent) =
+      var data : uint64 = 1
+      if posix.write(ev.efd, addr data, sizeof(uint64)) == -1:
+        raiseOSError(osLastError())
+
+    proc close*(ev: SelectEvent) =
+      discard posix.close(ev.efd)
+      deallocShared(cast[pointer](ev))
+
+    proc newEvent*(): SelectEvent =
+      var fdi = eventfd(0, 0)
+      if fdi == -1:
+        raiseOSError(osLastError())
+      setNonBlocking(fdi)
+      result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl)))
+      result.efd = cint(fdi)
+
+    proc selectInto*[T](s: Selector[T], timeout: int,
+                     results: var openarray[ReadyKey[T]]): int =
+      var
+        resultsTable: array[MAX_EPOLL_RESULT_EVENTS, epoll_event]
+
+      var maxResults = MAX_EPOLL_RESULT_EVENTS
+      if maxResults > len(results):
+        maxResults = len(results)
+
+      var count = epoll_wait(s.epollFD, addr(resultsTable[0]), maxResults.cint,
+                             timeout.cint)
+      if count > 0:
+        var i = 0
+        var k = 0
+        while i < count:
+          var events: set[Event] = {}
+          let fdi = int(resultsTable[i].data.u64)
+          var skey = addr(s.fds[fdi])
+          let pevents = resultsTable[i].events
+          var flags = s.fds[fdi].flags
+
+          if skey.ident != 0 and flags != {}:
+            block processItem:
+              if (pevents and EPOLLERR) != 0 or (pevents and EPOLLHUP) != 0:
+                events = events + {Event.Error}
+              if (pevents and EPOLLOUT) != 0:
+                events = events + {Event.Write}
+              if (pevents and EPOLLIN) != 0:
+                if Event.flagHandle in flags:
+                  events = events + {Event.Read}
+                elif Event.flagTimer in flags:
+                  var data: uint64 = 0
+                  if posix.read(fdi.cint, addr data,
+                                sizeof(uint64)) != sizeof(uint64):
+                    raiseOSError(osLastError())
+                  events = events + {Event.Timer}
+                elif Event.flagSignal in flags:
+                  var data: SignalFdInfo
+                  if posix.read(fdi.cint, addr data,
+                                sizeof(SignalFdInfo)) != sizeof(SignalFdInfo):
+                    raiseOsError(osLastError())
+                  events = events + {Event.Signal}
+                elif Event.flagProcess in flags:
+                  var data: SignalFdInfo
+                  if posix.read(fdi.cint, addr data,
+                                sizeof(SignalFdInfo)) != sizeof(SignalFdInfo):
+                    raiseOsError(osLastError())
+                  if cast[int](data.ssi_pid) == skey.param:
+                    events = events + {Event.Process}
+                    # we want to free resources for this event
+                    flags = flags + {Event.flagOneshot}
+                  else:
+                    break processItem
+                elif Event.flagUser in flags:
+                  var data: uint = 0
+                  if posix.read(fdi.cint, addr data,
+                                sizeof(uint)) != sizeof(uint):
+                    let err = osLastError()
+                    if err == OSErrorCode(EAGAIN):
+                      # someone already consumed event data
+                      inc(i)
+                      continue
+                    else:
+                      raiseOSError(err)
+                  events = events + {Event.User}
+                else:
+                  raise newException(ValueError,
+                                     "Unsupported epoll event in queue")
+              results[k].fd = skey.key.fd
+              results[k].events = events
+              results[k].data = skey.key.data
+
+              if Event.flagOneshot in flags:
+                var epv: epoll_event
+                try:
+                  if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint,
+                               addr epv) == -1:
+                    raiseOSError(osLastError())
+                finally:
+                  discard posix.close(fdi.cint)
+                  s.fds[fdi].ident = 0
+                  s.fds[fdi].flags = {}
+                  dec(s.count)
+              inc(k)
+          inc(i)
+        result = k
+      elif count == 0:
+        discard
+      else:
+        result = 0
+        let err = osLastError()
+        if cint(err) != EINTR:
+          raiseOSError(err)
+
+    proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] =
+      result = newSeq[ReadyKey[T]](MAX_EPOLL_RESULT_EVENTS)
+      var count = selectInto(s, timeout, result)
+      result.setLen(count)
+
+  #
+  # Windows select
+  #
+
+  elif defined(windows):
+    const FD_SETSIZE = 64
+
+    import hashes, nativesockets
+
+    when hasThreadSupport:
+      import sharedtables
+    else:
+      import tables
+
+    proc hash*(x: SocketHandle): Hash {.borrow.}
+    proc `$`*(x: SocketHandle): string {.borrow.}
+
+    proc WSAFDIsSet(s: SocketHandle, fdSet: var TFdSet): bool {.
+      stdcall, importc: "__WSAFDIsSet", dynlib: "ws2_32.dll", noSideEffect.}
+
+    template iFD_ISSET(s: SocketHandle, fdSet: var TFdSet): bool =
+      if WSAFDIsSet(s, fdSet): true else: false
+
+    template iFD_SET(s: SocketHandle, fdSet: var TFdSet) =
+      block:
+        var i = 0
+        while i < fdSet.fd_count:
+          if fdSet.fd_array[i] == s:
+            break
+          inc(i)
+        if i == fdSet.fd_count:
+          if fdSet.fd_count < ioselectors.FD_SETSIZE:
+            fdSet.fd_array[i] = s
+            inc(fdSet.fd_count)
+
+    template iFD_CLR(s: SocketHandle, fdSet: var TFdSet) =
+      block:
+        var i = 0
+        while i < fdSet.fd_count:
+          if fdSet.fd_array[i] == s:
+            if i == fdSet.fd_count - 1:
+              fdSet.fd_array[i] = 0.SocketHandle
+            else:
+              while i < (fdSet.fd_count - 1):
+                fdSet.fd_array[i] = fdSet.fd_array[i + 1]
+                inc(i)
+            dec(fdSet.fd_count)
+            break
+          inc(i)
+
+    template iFD_ZERO(fdSet: var TFdSet) =
+      fdSet.fd_count = 0
+
+    when hasThreadSupport:
+      type
+        SelectorImpl[T] = object
+          rSet: TFdSet
+          wSet: TFdSet
+          eSet: TFdSet
+          maxFD: uint
+          fds: SharedTable[SocketHandle, SelectorKey[T]]
+          count: int
+          lock: Lock
+    else:
+      type
+        SelectorImpl[T] = object
+          rSet: TFdSet
+          wSet: TFdSet
+          eSet: TFdSet
+          maxFD: uint
+          fds: Table[SocketHandle, SelectorKey[T]]
+          count: int
+
+    when hasThreadSupport:
+      type Selector*[T] = ptr SelectorImpl[T]
+    else:
+      type Selector*[T] = ref SelectorImpl[T]
+
+    type
+      SelectEventImpl = object
+        rsock: SocketHandle
+        wsock: SocketHandle
+
+    type SelectEvent* = ptr SelectEventImpl
+
+    when hasThreadSupport:
+      template withSelectLock[T](s: Selector[T], body: untyped) =
+        acquire(s.lock)
+        {.locks: [s.lock].}:
+          try:
+            body
+          finally:
+            release(s.lock)
+    else:
+      template withSelectLock[T](s: Selector[T], body: untyped) =
+        body
+
+    proc newSelector*[T](): Selector[T] =
+      var maxFD = FD_SETSIZE
+      when hasThreadSupport:
+        result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T])))
+        result.maxFD = maxFD.uint
+        result.fds = initSharedTable[SocketHandle, SelectorKey[T]]()
+        initLock result.lock
+      else:
+        result = Selector[T](maxFD: FD_SETSIZE)
+        result.maxFD = maxFD.uint
+        result.fds = initTable[SocketHandle, SelectorKey[T]]()
+
+      iFD_ZERO(result.rSet)
+      iFD_ZERO(result.wSet)
+      iFD_ZERO(result.eSet)
+
+    proc close*(s: Selector) =
+      when hasThreadSupport:
+        deinitSharedTable(s.fds)
+        deallocShared(cast[pointer](s))
+
+    template isEmpty*[T](s: Selector[T]): bool =
+      (s.count == 0)
+
+    template selectAdd[T](s: Selector[T], fd: SocketHandle,
+                          events: set[Event]) =
+      mixin withSelectLock
+      s.withSelectLock():
+        if Event.Read in events:
+          if s.rSet.fd_count == FD_SETSIZE:
+            raise newException(ValueError, "Maximum numbers of fds exceeded")
+          iFD_SET(fd, s.rSet)
+          inc(s.count)
+        if Event.Write in events:
+          if s.wSet.fd_count == FD_SETSIZE:
+            raise newException(ValueError, "Maximum numbers of fds exceeded")
+          iFD_SET(fd, s.wSet)
+          iFD_SET(fd, s.eSet)
+          inc(s.count)
+
+    proc registerHandle*[T](s: Selector[T], fd: SocketHandle,
+                            events: set[Event], data: T) =
+      var fdi = int(fd)
+      var flags = {Event.flagHandle} + events
+      var nkey = SelectorKey[T](ident: fdi, flags: flags)
+      nkey.key.fd = fdi
+      nkey.key.data = data
+
+      if s.fds.hasKeyOrPut(fd, nkey):
+        raise newException(ValueError, "Re-use of non closed descriptor")
+      selectAdd(s, fd, flags)
+
+    proc updateHandle*[T](s: Selector[T], fd: SocketHandle,
+                          events: set[Event]) =
+      s.withSelectLock():
+        withValue(s.fds, fd, skey) do:
+          if Event.flagHandle in skey.flags:
+            var oe = skey.flags
+            var ne = events + {Event.flagHandle}
+            if oe != ne:
+              if (Event.Read in oe) and (Event.Read notin ne):
+                iFD_CLR(fd, s.rSet)
+                dec(s.count)
+              if (Event.Write in oe) and (Event.Write notin ne):
+                iFD_CLR(fd, s.wSet)
+                iFD_CLR(fd, s.eSet)
+                dec(s.count)
+              if (Event.Read notin oe) and (Event.Read in ne):
+                iFD_SET(fd, s.rSet)
+                inc(s.count)
+              if (Event.Write notin oe) and (Event.Write in ne):
+                iFD_SET(fd, s.wSet)
+                iFD_SET(fd, s.eSet)
+                inc(s.count)
+              skey.flags = ne
+          else:
+            raise newException(ValueError,
+                               "Could not update non-handle descriptor")
+        do:
+          raise newException(ValueError,
+                             "Descriptor is not registered in queue")
+
+    proc registerTimer*[T](s: Selector, timeout: int, oneshot: bool,
+                           data: T): int {.discardable.} =
+      raise newException(ValueError, "Not implemented")
+
+    proc registerSignal*[T](s: Selector, signal: int,
+                            data: T): int {.discardable.} =
+      raise newException(ValueError, "Not implemented")
+
+    proc registerProcess*[T](s: Selector, pid: int,
+                             data: T): int {.discardable.} =
+      raise newException(ValueError, "Not implemented")
+
+    proc flush*[T](s: Selector[T]) = discard
+
+    proc unregister*[T](s: Selector[T], ev: SelectEvent) =
+      let fd = ev.rsock
+      s.withSelectLock():
+        iFD_CLR(fd, s.rSet)
+        dec(s.count)
+        s.fds.del(fd)
+
+
+    proc unregister*[T](s: Selector[T], fd: SocketHandle) =
+      s.withSelectLock():
+        s.fds.withValue(fd, skey) do:
+          if Event.Read in skey.flags:
+            iFD_CLR(fd, s.rSet)
+            dec(s.count)
+          if Event.Write in skey.flags:
+            iFD_CLR(fd, s.wSet)
+            iFD_CLR(fd, s.eSet)
+            dec(s.count)
+        s.fds.del(fd)
+
+    proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
+      var flags = {Event.flagUser, Event.Read}
+      var nkey = SelectorKey[T](ident: ev.rsock.int, flags: flags)
+      nkey.key.fd = ev.rsock.int
+      nkey.key.data = data
+      if s.fds.hasKeyOrPut(ev.rsock, nkey):
+        raise newException(ValueError, "Re-use of non closed descriptor")
+      selectAdd(s, ev.rsock, flags)
+
+    proc newEvent*(): SelectEvent =
+      var ssock = newNativeSocket()
+      var wsock = newNativeSocket()
+      var rsock: SocketHandle = INVALID_SOCKET
+      var saddr = Sockaddr_in()
+      try:
+        saddr.sin_family = winlean.AF_INET
+        saddr.sin_port = 0
+        saddr.sin_addr.s_addr = INADDR_ANY
+        if bindAddr(ssock, cast[ptr SockAddr](addr(saddr)),
+                    sizeof(saddr).SockLen) < 0'i32:
+          raiseOSError(osLastError())
+
+        if winlean.listen(ssock, 1) == -1:
+          raiseOSError(osLastError())
+
+        var namelen = sizeof(saddr).SockLen
+        if getsockname(ssock, cast[ptr SockAddr](addr(saddr)),
+                       addr(namelen)) == -1'i32:
+          raiseOSError(osLastError())
+
+        saddr.sin_addr.s_addr = 0x0100007F
+        if winlean.connect(wsock, cast[ptr SockAddr](addr(saddr)),
+                           sizeof(saddr).SockLen) == -1:
+          raiseOSError(osLastError())
+        namelen = sizeof(saddr).SockLen
+        rsock = winlean.accept(ssock, cast[ptr SockAddr](addr(saddr)),
+                               cast[ptr SockLen](addr(namelen)))
+        if rsock == SocketHandle(-1):
+          raiseOSError(osLastError())
+
+        if winlean.closesocket(ssock) == -1:
+          raiseOSError(osLastError())
+
+        var mode = clong(1)
+        if ioctlsocket(rsock, FIONBIO, addr(mode)) == -1:
+          raiseOSError(osLastError())
+        mode = clong(1)
+        if ioctlsocket(wsock, FIONBIO, addr(mode)) == -1:
+          raiseOSError(osLastError())
+
+        result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl)))
+        result.rsock = rsock
+        result.wsock = wsock
+      except:
+        discard winlean.closesocket(ssock)
+        discard winlean.closesocket(wsock)
+        if rsock != INVALID_SOCKET:
+          discard winlean.closesocket(rsock)
+
+    proc setEvent*(ev: SelectEvent) =
+      var data: int = 1
+      if winlean.send(ev.wsock, cast[pointer](addr data),
+                      cint(sizeof(int)), 0) != sizeof(int):
+        raiseOSError(osLastError())
+
+    proc close*(ev: SelectEvent) =
+      discard winlean.closesocket(ev.rsock)
+      discard winlean.closesocket(ev.wsock)
+      deallocShared(cast[pointer](ev))
+
+    proc selectInto*[T](s: Selector[T], timeout: int,
+                     results: var openarray[ReadyKey[T]]): int =
+      var tv = Timeval()
+      var ptv = addr tv
+      var rset, wset, eset: TFdSet
+
+      if timeout != -1:
+        tv.tv_sec = timeout.int32 div 1_000
+        tv.tv_usec = (timeout.int32 %% 1_000) * 1_000
+      else:
+        ptv = nil
+
+      s.withSelectLock():
+        rset = s.rSet
+        wset = s.wSet
+        eset = s.eSet
+
+      var count = select(cint(0), addr(rset), addr(wset),
+                         addr(eset), ptv).int
+      if count > 0:
+        var rindex = 0
+        var i = 0
+        while i < rset.fd_count:
+          let fd = rset.fd_array[i]
+          if iFD_ISSET(fd, rset):
+            var events = {Event.Read}
+            if iFD_ISSET(fd, eset): events = events + {Event.Error}
+            if iFD_ISSET(fd, wset): events = events + {Event.Write}
+            s.fds.withValue(fd, skey) do:
+              if Event.flagHandle in skey.flags:
+                skey.key.events = events
+              elif Event.flagUser in skey.flags:
+                var data: int = 0
+                if winlean.recv(fd, cast[pointer](addr(data)),
+                                sizeof(int).cint, 0) != sizeof(int):
+                  let err = osLastError()
+                  if err != OSErrorCode(WSAEWOULDBLOCK):
+                    raiseOSError(err)
+                  else:
+                    # someone already consumed event data
+                    inc(i)
+                    continue
+                skey.key.events = {Event.User}
+              results[rindex].fd = skey.key.fd
+              results[rindex].data = skey.key.data
+              results[rindex].events = skey.key.events
+              inc(rindex)
+          inc(i)
+
+        i = 0
+        while i < wset.fd_count:
+          let fd = wset.fd_array[i]
+          if iFD_ISSET(fd, wset):
+            var events = {Event.Write}
+            if not iFD_ISSET(fd, rset):
+              if iFD_ISSET(fd, eset): events = events + {Event.Error}
+              s.fds.withValue(fd, skey) do:
+                skey.key.events = events
+                results[rindex].fd = skey.key.fd
+                results[rindex].data = skey.key.data
+                results[rindex].events = skey.key.events
+                inc(rindex)
+          inc(i)
+        count = rindex
+      elif count == 0:
+        discard
+      else:
+        raiseOSError(osLastError())
+      result = count
+
+    proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] =
+      result = newSeq[ReadyKey[T]](FD_SETSIZE)
+      var count = selectInto(s, timeout, result)
+      result.setLen(count)
+
+  #
+  # Posix poll
+  #
+
+  else:
+    # Maximum number of events that can be returned
+    const MAX_POLL_RESULT_EVENTS = 64
+
+    type
+      SelectorImpl[T] = object
+        maxFD : uint
+        pollcnt: int
+        fds: ptr SharedArray[SelectorKey[T]]
+        pollfds: ptr SharedArray[TPollFd]
+        count: int
+        when hasThreadSupport:
+          lock: Lock
+
+      Selector*[T] = ptr SelectorImpl[T]
+
+      SelectEventImpl = object
+        rfd: cint
+        wfd: cint
+
+      SelectEvent* = ptr SelectEventImpl
+
+    when hasThreadSupport:
+      template withPollLock[T](s: Selector[T], body: untyped) =
+        acquire(s.lock)
+        {.locks: [s.lock].}:
+          try:
+            body
+          finally:
+            release(s.lock)
+    else:
+      template withPollLock(s, body: untyped) =
+        body
+
+    proc newSelector*[T](): Selector[T] =
+      var maxFD = getMaxFds()
+
+      result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T])))
+      result.maxFD = maxFD.uint
+      result.fds = allocSharedArray[SelectorKey[T]](maxFD)
+      result.pollfds = allocSharedArray[TPollFd](maxFD)
+      when hasThreadSupport:
+        initLock(result.lock)
+
+    proc close*[T](s: Selector[T]) =
+      when hasThreadSupport:
+        deinitLock(s.lock)
+      deallocSharedArray(s.fds)
+      deallocSharedArray(s.pollfds)
+      deallocShared(cast[pointer](s))
+
+    template pollAdd[T](s: Selector[T], sock: cint, events: set[Event]) =
+      withPollLock(s):
+        var pollev: cshort = 0
+        if Event.Read in events: pollev = pollev or POLLIN
+        if Event.Write in events: pollev = pollev or POLLOUT
+        s.pollfds[s.pollcnt].fd = cint(sock)
+        s.pollfds[s.pollcnt].events = pollev
+        inc(s.count)
+        inc(s.pollcnt)
+
+    template pollUpdate[T](s: Selector[T], sock: cint, events: set[Event]) =
+      withPollLock(s):
+        var i = 0
+        var pollev: cshort = 0
+        if Event.Read in events: pollev = pollev or POLLIN
+        if Event.Write in events: pollev = pollev or POLLOUT
+
+        while i < s.pollcnt:
+          if s.pollfds[i].fd == sock:
+            s.pollfds[i].events = pollev
+            break
+          inc(i)
+
+        if i == s.pollcnt:
+          raise newException(ValueError,
+                             "Descriptor is not registered in queue")
+
+    template pollRemove[T](s: Selector[T], sock: cint) =
+      withPollLock(s):
+        var i = 0
+        while i < s.pollcnt:
+          if s.pollfds[i].fd == sock:
+            if i == s.pollcnt - 1:
+              s.pollfds[i].fd = 0
+              s.pollfds[i].events = 0
+              s.pollfds[i].revents = 0
+            else:
+              while i < (s.pollcnt - 1):
+                s.pollfds[i].fd = s.pollfds[i + 1].fd
+                s.pollfds[i].events = s.pollfds[i + 1].events
+                inc(i)
+            break
+          inc(i)
+        dec(s.pollcnt)
+        dec(s.count)
+
+    proc registerHandle*[T](s: Selector[T], fd: SocketHandle,
+                            events: set[Event], data: T) =
+      var fdi = int(fd)
+      s.checkMaxFd(fdi)
+      doAssert(s.fds[fdi].ident == 0)
+      setKey(s, fdi, fdi, {Event.flagHandle} + events, 0, data)
+      s.pollAdd(fdi.cint, events)
+
+    proc updateHandle*[T](s: Selector[T], fd: SocketHandle,
+                          events: set[Event]) =
+      var fdi = int(fd)
+      s.checkMaxFd(fdi)
+      var oe = s.fds[fdi].flags
+      doAssert(s.fds[fdi].ident != 0)
+      doAssert(Event.flagHandle in oe)
+      var ne = events + {Event.flagHandle}
+      if ne != oe:
+        if events != {}:
+          s.pollUpdate(fd.cint, events)
+        else:
+          s.pollRemove(fd.cint)
+        s.fds[fdi].flags = ne
+
+    proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool,
+                           data: T): int {.discardable.} =
+      raise newException(ValueError, "Not implemented")
+
+    proc registerSignal*[T](s: Selector[T], signal: int,
+                            data: T): int {.discardable.} =
+      raise newException(ValueError, "Not implemented")
+
+    proc registerProcess*[T](s: Selector[T], pid: int,
+                             data: T): int {.discardable.} =
+      raise newException(ValueError, "Not implemented")
+
+    proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
+      var fdi = int(ev.rfd)
+      doAssert(s.fds[fdi].ident == 0)
+      var events = {Event.flagUser, Event.Read}
+      setKey(s, fdi, fdi, events, 0, data)
+      s.pollAdd(fdi.cint, events)
+
+    proc flush*[T](s: Selector[T]) = discard
+
+    template isEmpty*[T](s: Selector[T]): bool =
+      (s.count == 0)
+
+    proc unregister*[T](s: Selector[T], fd: int|SocketHandle|cint) =
+      var fdi = int(fd)
+      if fdi.uint < s.maxFD:
+        if s.fds[fdi].ident != 0 and s.fds[fdi].flags != {}:
+          clearKey(s, fdi)
+          s.pollRemove(fdi.cint)
+
+    proc unregister*[T](s: Selector[T], ev: SelectEvent) =
+      var fdi = int(ev.rfd)
+      if fdi.uint < s.maxFD:
+        if s.fds[fdi].ident != 0 and (Event.flagUser in s.fds[fdi].flags):
+          clearKey(s, fdi)
+          s.pollRemove(fdi.cint)
+
+    proc newEvent*(): SelectEvent =
+      var fds: array[2, cint]
+      if posix.pipe(fds) == -1:
+        raiseOSError(osLastError())
+      setNonBlocking(fds[0])
+      setNonBlocking(fds[1])
+      result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl)))
+      result.rfd = fds[0]
+      result.wfd = fds[1]
+
+    proc setEvent*(ev: SelectEvent) =
+      var data: int = 1
+      if posix.write(ev.wfd, addr data, sizeof(int)) != sizeof(int):
+        raiseOSError(osLastError())
+
+    proc close*(ev: SelectEvent) =
+      discard posix.close(cint(ev.rfd))
+      discard posix.close(cint(ev.wfd))
+      deallocShared(cast[pointer](ev))
+
+    proc selectInto*[T](s: Selector[T], timeout: int,
+                        results: var openarray[ReadyKey[T]]): int =
+      var maxResults = MAX_POLL_RESULT_EVENTS
+      if maxResults > len(results):
+        maxResults = len(results)
+
+      s.withPollLock():
+        var count = posix.poll(addr(s.pollfds[0]), Tnfds(s.pollcnt), timeout)
+        if count > 0:
+          var i = 0
+          var k = 0
+          var rindex = 0
+          while (i < s.pollcnt) and (k < count) and (rindex < maxResults):
+            let revents = s.pollfds[i].revents
+            let fd = s.pollfds[i].fd
+            if revents != 0:
+              var events: set[Event] = {}
+              if (revents and POLLIN) != 0:
+                events = events + {Event.Read}
+              if (revents and POLLOUT) != 0:
+                events = events + {Event.Write}
+              if (revents and POLLERR) != 0 or (revents and POLLHUP) != 0 or
+                 (revents and POLLNVAL) != 0:
+                events = events + {Event.Error}
+
+              var skey = addr(s.fds[fd])
+              if Event.flagUser in skey.flags:
+                if Event.Read in events:
+                  var data: int = 0
+                  if posix.read(fd, addr data, sizeof(int)) != sizeof(int):
+                    let err = osLastError()
+                    if err != OSErrorCode(EAGAIN):
+                      raiseOSError(osLastError())
+                    else:
+                      # someone already consumed event data
+                      inc(i)
+                      continue
+                  events = {Event.User}
+
+              results[rindex].fd = fd
+              results[rindex].events = events
+              results[rindex].data = skey.key.data
+              s.pollfds[i].revents = 0
+              inc(rindex)
+              inc(k)
+            inc(i)
+          result = k
+        elif count == 0:
+          discard
+        else:
+          let err = osLastError()
+          if err.cint == EINTR:
+            discard
+          else:
+            raiseOSError(osLastError())
+
+    proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] =
+      result = newSeq[ReadyKey[T]](MAX_POLL_RESULT_EVENTS)
+      var count = selectInto(s, timeout, result)
+      result.setLen(count)
+
+  when not defined(windows):
+    template withData*[T](s: Selector[T], fd: SocketHandle, value,
+                          body: untyped) =
+      var fdi = int(fd)
+      s.checkMaxFd(fdi)
+      if s.fds[fdi].ident != 0:
+        var value = addr(s.fds[fdi].key.data)
+        body
+
+    template withData*[T](s: Selector[T], fd: SocketHandle, value, body1,
+                          body2: untyped) =
+      var fdi = int(fd)
+      s.checkMaxFd(fdi)
+      if s.fds[fdi].ident != 0:
+        var value = addr(s.fds[fdi].key.data)
+        body1
+      else:
+        body2
+  else:
+    template withData*(s: Selector, fd: SocketHandle, value, body: untyped) =
+      s.fds.withValue(fd, skey) do:
+        var value {.inject.} = addr(skey.key.data)
+        body
+
+    template withData*(s: Selector, fd: SocketHandle, value,
+                       body1, body2: untyped) =
+      s.fds.withValue(fd, skey) do:
+        var value {.inject.} = addr(skey.key.data)
+        body1
+      do:
+        body2
diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim
index 60f064e7c..c135f6a00 100644
--- a/lib/pure/terminal.nim
+++ b/lib/pure/terminal.nim
@@ -493,17 +493,21 @@ template styledEcho*(args: varargs[expr]): expr =
   ## Echoes styles arguments to stdout using ``styledWriteLine``.
   callStyledEcho(args)
 
-when defined(nimdoc):
-  proc getch*(): char =
-    ## Read a single character from the terminal, blocking until it is entered.
-    ## The character is not printed to the terminal. This is not available for
-    ## Windows.
-    discard
-elif not defined(windows):
-  proc getch*(): char =
-    ## Read a single character from the terminal, blocking until it is entered.
-    ## The character is not printed to the terminal. This is not available for
-    ## Windows.
+proc getch*(): char =
+  ## Read a single character from the terminal, blocking until it is entered.
+  ## The character is not printed to the terminal.
+  when defined(windows):
+    let fd = getStdHandle(STD_INPUT_HANDLE)
+    var keyEvent = KEY_EVENT_RECORD()
+    var numRead: cint 
+    while true:
+      # Block until character is entered
+      doAssert(waitForSingleObject(fd, INFINITE) == WAIT_OBJECT_0)
+      doAssert(readConsoleInput(fd, addr(keyEvent), 1, addr(numRead)) != 0)
+      if numRead == 0 or keyEvent.eventType != 1 or keyEvent.bKeyDown == 0:
+        continue
+      return char(keyEvent.uChar)
+  else:
     let fd = getFileHandle(stdin)
     var oldMode: Termios
     discard fd.tcgetattr(addr oldMode)