summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-x[-rw-r--r--]bootstrap.sh2
-rw-r--r--compiler/sigmatch.nim25
-rw-r--r--doc/nep1.rst6
-rw-r--r--lib/pure/ioselectors.nim1764
-rw-r--r--tests/async/tioselectors.nim407
-rw-r--r--tests/async/tioselectors.nim.cfg1
-rw-r--r--tests/generics/t88.nim25
-rw-r--r--web/assets/bountysource/xored.pngbin0 -> 6258 bytes
-rw-r--r--web/news.rst3
-rw-r--r--web/news/2016_06_23_launching_the_2016_nim_community_survey.rst29
-rw-r--r--web/ticker.html10
11 files changed, 2260 insertions, 12 deletions
diff --git a/bootstrap.sh b/bootstrap.sh
index 7f19c2440..dd551b52d 100644..100755
--- a/bootstrap.sh
+++ b/bootstrap.sh
@@ -18,5 +18,3 @@ set +x
 
 echo
 echo 'Install Nim using "./install.sh <dir>" or "sudo ./install.sh <dir>".'
-
-exit 0
diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim
index df27e3c1d..70fbe7358 100644
--- a/compiler/sigmatch.nim
+++ b/compiler/sigmatch.nim
@@ -363,6 +363,25 @@ proc isObjectSubtype(a, f: PType): int =
   if t != nil:
     result = depth
 
+proc skipToGenericBody(t: PType): PType =
+  var r = t
+  while r != nil:
+    if r.kind in {tyGenericInst, tyGenericInvocation}:
+      return r.sons[0]
+    r = if r.len > 0: r.lastSon else: nil
+
+proc isGenericSubtype(a, f: PType, d: var int): bool =
+  assert f.kind in {tyGenericInst, tyGenericInvocation, tyGenericBody}
+  var t = if a.kind == tyGenericBody: a else: a.skipToGenericBody
+  var r = if f.kind == tyGenericBody: f else: f.skipToGenericBody
+  var depth = 0
+  while t != nil and not sameObjectTypes(r, t):
+    t = t.skipToGenericBody
+    inc depth
+  if t != nil:
+    d = depth
+    result = true
+
 proc minRel(a, b: TTypeRelation): TTypeRelation =
   if a <= b: result = a
   else: result = b
@@ -647,7 +666,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
   template bindingRet(res) =
     if doBind:
       let bound = aOrig.skipTypes({tyRange}).skipIntLit
-      if doBind: put(c.bindings, f, bound)
+      put(c.bindings, f, bound)
     return res
 
   template considerPreviousT(body: stmt) {.immediate.} =
@@ -945,17 +964,19 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
 
   of tyGenericInvocation:
     var x = a.skipGenericAlias
+    var depth = 0
     if x.kind == tyGenericInvocation or f.sons[0].kind != tyGenericBody:
       #InternalError("typeRel: tyGenericInvocation -> tyGenericInvocation")
       # simply no match for now:
       discard
     elif x.kind == tyGenericInst and
-          (f.sons[0] == x.sons[0]) and
+          ((f.sons[0] == x.sons[0]) or isGenericSubtype(x, f, depth)) and
           (sonsLen(x) - 1 == sonsLen(f)):
       for i in countup(1, sonsLen(f) - 1):
         if x.sons[i].kind == tyGenericParam:
           internalError("wrong instantiated type!")
         elif typeRel(c, f.sons[i], x.sons[i]) <= isSubtype: return
+      c.inheritancePenalty += depth
       result = isGeneric
     else:
       let genericBody = f.sons[0]
diff --git a/doc/nep1.rst b/doc/nep1.rst
index b4bd6309c..b5991ba9e 100644
--- a/doc/nep1.rst
+++ b/doc/nep1.rst
@@ -125,8 +125,8 @@ changed in the future.
 Coding Conventions
 ------------------
 
-- The 'return' statement should only be used when it's control-flow properties
-  are required. Use a procedures implicit 'result' variable instead. This
+- The 'return' statement should only be used when its control-flow properties
+  are required. Use a procedure's implicit 'result' variable instead. This
   improves readability.
 
 - Prefer to return `[]` and `""` instead of `nil`, or throw an exception if
@@ -150,7 +150,7 @@ Conventions for multi-line statements and expressions
 
 - Any tuple type declarations that are longer than one line should use the
   regular object type layout instead. This enhances the readability of the
-  tuple declaration by splitting its members information across multiple lines.
+  tuple declaration by splitting its members' information across multiple lines.
 
   .. code-block:: nim
     type
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/tests/async/tioselectors.nim b/tests/async/tioselectors.nim
new file mode 100644
index 000000000..ebfe10fd6
--- /dev/null
+++ b/tests/async/tioselectors.nim
@@ -0,0 +1,407 @@
+discard """
+  file: "tioselectors.nim"
+  output: "All tests passed!"
+"""
+import ioselectors
+
+const hasThreadSupport = compileOption("threads")
+
+template processTest(t, x: untyped) =
+  #stdout.write(t)
+  #stdout.flushFile()
+  if not x: echo(t & " FAILED\r\n")
+
+when not defined(windows):
+  import os, posix, osproc, nativesockets, times
+
+  const supportedPlatform = defined(macosx) or defined(freebsd) or
+                            defined(netbsd) or defined(openbsd) or
+                            defined(linux)
+
+  proc socket_notification_test(): bool =
+    proc create_test_socket(): SocketHandle =
+      var sock = posix.socket(posix.AF_INET, posix.SOCK_STREAM,
+                              posix.IPPROTO_TCP)
+      var x: int = fcntl(sock, F_GETFL, 0)
+      if x == -1: raiseOSError(osLastError())
+      else:
+        var mode = x or O_NONBLOCK
+        if fcntl(sock, F_SETFL, mode) == -1:
+          raiseOSError(osLastError())
+      result = sock
+
+    var client_message = "SERVER HELLO =>"
+    var server_message = "CLIENT HELLO"
+    var buffer : array[128, char]
+
+    var selector = newSelector[int]()
+    var client_socket = create_test_socket()
+    var server_socket = create_test_socket()
+
+    registerHandle(selector, server_socket, {Event.Read}, 0)
+    registerHandle(selector, client_socket, {Event.Write}, 0)
+
+    var option : int32 = 1
+    if setsockopt(server_socket, cint(SOL_SOCKET), cint(SO_REUSEADDR),
+                  addr(option), sizeof(option).SockLen) < 0:
+      raiseOSError(osLastError())
+
+    var aiList = getAddrInfo("0.0.0.0", Port(13337))
+    if bindAddr(server_socket, aiList.ai_addr,
+                aiList.ai_addrlen.Socklen) < 0'i32:
+      dealloc(aiList)
+      raiseOSError(osLastError())
+    discard server_socket.listen()
+    dealloc(aiList)
+
+    aiList = getAddrInfo("127.0.0.1", Port(13337))
+    discard posix.connect(client_socket, aiList.ai_addr,
+                          aiList.ai_addrlen.Socklen)
+    dealloc(aiList)
+    var rc1 = selector.select(100)
+    assert(len(rc1) == 2)
+
+    var sockAddress: SockAddr
+    var addrLen = sizeof(sockAddress).Socklen
+    var server2_socket = accept(server_socket,
+                                cast[ptr SockAddr](addr(sockAddress)),
+                                addr(addrLen))
+    assert(server2_socket != osInvalidSocket)
+    selector.registerHandle(server2_socket, {Event.Read}, 0)
+
+    if posix.send(client_socket, addr(client_message[0]),
+                  len(client_message), 0) == -1:
+      raiseOSError(osLastError())
+
+    selector.updateHandle(client_socket, {Event.Read})
+
+    var rc2 = selector.select(100)
+    assert(len(rc2) == 1)
+
+    var read_count = posix.recv(server2_socket, addr (buffer[0]), 128, 0)
+    if read_count == -1:
+      raiseOSError(osLastError())
+
+    assert(read_count == len(client_message))
+    var test1 = true
+    for i in 0..<read_count:
+      if client_message[i] != buffer[i]:
+        test1 = false
+        break
+    assert(test1)
+
+    selector.updateHandle(server2_socket, {Event.Write})
+    var rc3 = selector.select(0)
+    assert(len(rc3) == 1)
+    if posix.send(server2_socket, addr(server_message[0]),
+                  len(server_message), 0) == -1:
+      raiseOSError(osLastError())
+    selector.updateHandle(server2_socket, {Event.Read})
+
+    var rc4 = selector.select(100)
+    assert(len(rc4) == 1)
+    read_count = posix.recv(client_socket, addr(buffer[0]), 128, 0)
+    if read_count == -1:
+      raiseOSError(osLastError())
+
+    assert(read_count == len(server_message))
+    var test2 = true
+    for i in 0..<read_count:
+      if server_message[i] != buffer[i]:
+        test2 = false
+        break
+    assert(test2)
+
+    selector.unregister(server_socket)
+    selector.unregister(server2_socket)
+    selector.unregister(client_socket)
+    discard posix.close(server_socket)
+    discard posix.close(server2_socket)
+    discard posix.close(client_socket)
+    assert(selector.isEmpty())
+    close(selector)
+    result = true
+
+  proc event_notification_test(): bool =
+    var selector = newSelector[int]()
+    var event = newEvent()
+    selector.registerEvent(event, 1)
+    selector.flush()
+    event.setEvent()
+    var rc1 = selector.select(0)
+    event.setEvent()
+    var rc2 = selector.select(0)
+    var rc3 = selector.select(0)
+    assert(len(rc1) == 1 and len(rc2) == 1 and len(rc3) == 0)
+    var ev1 = rc1[0].data
+    var ev2 = rc2[0].data
+    assert(ev1 == 1 and ev2 == 1)
+    selector.unregister(event)
+    event.close()
+    assert(selector.isEmpty())
+    selector.close()
+    result = true
+
+  when supportedPlatform:
+    proc timer_notification_test(): bool =
+      var selector = newSelector[int]()
+      var timer = selector.registerTimer(100, false, 0)
+      var rc1 = selector.select(140)
+      var rc2 = selector.select(140)
+      assert(len(rc1) == 1 and len(rc2) == 1)
+      selector.unregister(timer)
+      selector.flush()
+      selector.registerTimer(100, true, 0)
+      var rc3 = selector.select(120)
+      var rc4 = selector.select(120)
+      assert(len(rc3) == 1 and len(rc4) == 0)
+      assert(selector.isEmpty())
+      selector.close()
+      result = true
+
+    proc process_notification_test(): bool =
+      var selector = newSelector[int]()
+      var process2 = startProcess("/bin/sleep", "", ["2"], nil,
+                           {poStdErrToStdOut, poUsePath})
+      discard startProcess("/bin/sleep", "", ["1"], nil,
+                           {poStdErrToStdOut, poUsePath})
+
+      selector.registerProcess(process2.processID, 0)
+      var rc1 = selector.select(1500)
+      var rc2 = selector.select(1500)
+      var r = len(rc1) + len(rc2)
+      assert(r == 1)
+      result = true
+
+    proc signal_notification_test(): bool =
+      var sigset1n, sigset1o, sigset2n, sigset2o: Sigset
+      var pid = posix.getpid()
+
+      discard sigemptyset(sigset1n)
+      discard sigemptyset(sigset1o)
+      discard sigemptyset(sigset2n)
+      discard sigemptyset(sigset2o)
+
+      when hasThreadSupport:
+        if pthread_sigmask(SIG_BLOCK, sigset1n, sigset1o) == -1:
+          raiseOSError(osLastError())
+      else:
+        if sigprocmask(SIG_BLOCK, sigset1n, sigset1o) == -1:
+          raiseOSError(osLastError())
+
+      var selector = newSelector[int]()
+      var s1 = selector.registerSignal(SIGUSR1, 1)
+      var s2 = selector.registerSignal(SIGUSR2, 2)
+      var s3 = selector.registerSignal(SIGTERM, 3)
+      selector.flush()
+
+      discard posix.kill(pid, SIGUSR1)
+      discard posix.kill(pid, SIGUSR2)
+      discard posix.kill(pid, SIGTERM)
+      var rc = selector.select(0)
+      selector.unregister(s1)
+      selector.unregister(s2)
+      selector.unregister(s3)
+
+      when hasThreadSupport:
+        if pthread_sigmask(SIG_BLOCK, sigset2n, sigset2o) == -1:
+          raiseOSError(osLastError())
+      else:
+        if sigprocmask(SIG_BLOCK, sigset2n, sigset2o) == -1:
+          raiseOSError(osLastError())
+
+      assert(len(rc) == 3)
+      assert(rc[0].data + rc[1].data + rc[2].data == 6) # 1 + 2 + 3
+      assert(equalMem(addr sigset1o, addr sigset2o, sizeof(Sigset)))
+      assert(selector.isEmpty())
+      result = true
+
+  when hasThreadSupport:
+
+    var counter = 0
+
+    proc event_wait_thread(event: SelectEvent) {.thread.} =
+      var selector = newSelector[int]()
+      selector.registerEvent(event, 1)
+      selector.flush()
+      var rc = selector.select(1000)
+      if len(rc) == 1:
+        inc(counter)
+      selector.unregister(event)
+      assert(selector.isEmpty())
+
+    proc mt_event_test(): bool =
+      var
+        thr: array [0..7, Thread[SelectEvent]]
+      var selector = newSelector[int]()
+      var sock = newNativeSocket()
+      var event = newEvent()
+      for i in 0..high(thr):
+        createThread(thr[i], event_wait_thread, event)
+      selector.registerHandle(sock, {Event.Read}, 1)
+      discard selector.select(500)
+      selector.unregister(sock)
+      event.setEvent()
+      joinThreads(thr)
+      assert(counter == 1)
+      result = true
+
+  processTest("Socket notification test...", socket_notification_test())
+  processTest("User event notification test...", event_notification_test())
+  when hasThreadSupport:
+    processTest("Multithreaded user event notification test...",
+                mt_event_test())
+  when supportedPlatform:
+    processTest("Timer notification test...", timer_notification_test())
+    processTest("Process notification test...", process_notification_test())
+    processTest("Signal notification test...", signal_notification_test())
+  echo("All tests passed!")
+else:
+  import nativesockets, winlean, os, osproc
+
+  proc socket_notification_test(): bool =
+    proc create_test_socket(): SocketHandle =
+      var sock = newNativeSocket()
+      setBlocking(sock, false)
+      result = sock
+
+    var client_message = "SERVER HELLO =>"
+    var server_message = "CLIENT HELLO"
+    var buffer : array[128, char]
+
+    var selector = newSelector[int]()
+    var client_socket = create_test_socket()
+    var server_socket = create_test_socket()
+
+    selector.registerHandle(server_socket, {Event.Read}, 0)
+    selector.registerHandle(client_socket, {Event.Write}, 0)
+
+    var option : int32 = 1
+    if setsockopt(server_socket, cint(SOL_SOCKET), cint(SO_REUSEADDR),
+                  addr(option), sizeof(option).SockLen) < 0:
+      raiseOSError(osLastError())
+
+    var aiList = getAddrInfo("0.0.0.0", Port(13337))
+    if bindAddr(server_socket, aiList.ai_addr,
+                aiList.ai_addrlen.Socklen) < 0'i32:
+      dealloc(aiList)
+      raiseOSError(osLastError())
+    discard server_socket.listen()
+    dealloc(aiList)
+
+    aiList = getAddrInfo("127.0.0.1", Port(13337))
+    discard connect(client_socket, aiList.ai_addr,
+                    aiList.ai_addrlen.Socklen)
+    dealloc(aiList)
+    # for some reason Windows select doesn't return both
+    # descriptors from first call, so we need to make 2 calls
+    discard selector.select(100)
+    var rcm = selector.select(100)
+    assert(len(rcm) == 2)
+
+    var sockAddress = SockAddr()
+    var addrLen = sizeof(sockAddress).Socklen
+    var server2_socket = accept(server_socket,
+                                cast[ptr SockAddr](addr(sockAddress)),
+                                addr(addrLen))
+    assert(server2_socket != osInvalidSocket)
+    selector.registerHandle(server2_socket, {Event.Read}, 0)
+
+    if send(client_socket, cast[pointer](addr(client_message[0])),
+            cint(len(client_message)), 0) == -1:
+      raiseOSError(osLastError())
+
+    selector.updateHandle(client_socket, {Event.Read})
+
+    var rc2 = selector.select(100)
+    assert(len(rc2) == 1)
+
+    var read_count = recv(server2_socket, addr (buffer[0]), 128, 0)
+    if read_count == -1:
+      raiseOSError(osLastError())
+
+    assert(read_count == len(client_message))
+    var test1 = true
+    for i in 0..<read_count:
+      if client_message[i] != buffer[i]:
+        test1 = false
+        break
+    assert(test1)
+
+    if send(server2_socket, cast[pointer](addr(server_message[0])),
+                  cint(len(server_message)), 0) == -1:
+      raiseOSError(osLastError())
+
+    var rc3 = selector.select(0)
+    assert(len(rc3) == 1)
+    read_count = recv(client_socket, addr(buffer[0]), 128, 0)
+    if read_count == -1:
+      raiseOSError(osLastError())
+
+    assert(read_count == len(server_message))
+    var test2 = true
+    for i in 0..<read_count:
+      if server_message[i] != buffer[i]:
+        test2 = false
+        break
+    assert(test2)
+
+    selector.unregister(server_socket)
+    selector.unregister(server2_socket)
+    selector.unregister(client_socket)
+    close(server_socket)
+    close(server2_socket)
+    close(client_socket)
+    assert(selector.isEmpty())
+    close(selector)
+    result = true
+
+  proc event_notification_test(): bool =
+    var selector = newSelector[int]()
+    var event = newEvent()
+    selector.registerEvent(event, 1)
+    selector.flush()
+    event.setEvent()
+    var rc1 = selector.select(0)
+    event.setEvent()
+    var rc2 = selector.select(0)
+    var rc3 = selector.select(0)
+    assert(len(rc1) == 1 and len(rc2) == 1 and len(rc3) == 0)
+    var ev1 = rc1[0].data
+    var ev2 = rc2[0].data
+    assert(ev1 == 1 and ev2 == 1)
+    selector.unregister(event)
+    event.close()
+    assert(selector.isEmpty())
+    selector.close()
+    result = true
+
+  when hasThreadSupport:
+    var counter = 0
+
+    proc event_wait_thread(event: SelectEvent) {.thread.} =
+      var selector = newSelector[int]()
+      selector.registerEvent(event, 1)
+      selector.flush()
+      var rc = selector.select(500)
+      if len(rc) == 1:
+        inc(counter)
+      selector.unregister(event)
+      assert(selector.isEmpty())
+
+    proc mt_event_test(): bool =
+      var thr: array [0..7, Thread[SelectEvent]]
+      var event = newEvent()
+      for i in 0..high(thr):
+        createThread(thr[i], event_wait_thread, event)
+      event.setEvent()
+      joinThreads(thr)
+      assert(counter == 1)
+      result = true
+
+  processTest("Socket notification test...", socket_notification_test())
+  processTest("User event notification test...", event_notification_test())
+  when hasThreadSupport:
+    processTest("Multithreaded user event notification test...",
+                 mt_event_test())
+  echo("All tests passed!")
diff --git a/tests/async/tioselectors.nim.cfg b/tests/async/tioselectors.nim.cfg
new file mode 100644
index 000000000..b1b896858
--- /dev/null
+++ b/tests/async/tioselectors.nim.cfg
@@ -0,0 +1 @@
+threads:on -d:threadsafe
diff --git a/tests/generics/t88.nim b/tests/generics/t88.nim
new file mode 100644
index 000000000..93d93f063
--- /dev/null
+++ b/tests/generics/t88.nim
@@ -0,0 +1,25 @@
+# Issue 88
+
+type
+  BaseClass[V] = object of RootObj
+    b: V
+
+proc new[V](t: typedesc[BaseClass], v: V): BaseClass[V] =
+  BaseClass[V](b: v)
+
+proc baseMethod[V](v: BaseClass[V]): V = v.b
+proc overridedMethod[V](v: BaseClass[V]): V = v.baseMethod
+
+type
+  ChildClass[V] = object of BaseClass[V]
+    c: V
+
+proc new[V](t: typedesc[ChildClass], v1, v2: V): ChildClass[V] =
+  ChildClass[V](b: v1, c: v2)
+
+proc overridedMethod[V](v: ChildClass[V]): V = v.c
+
+let c = ChildClass[string].new("Base", "Child")
+
+assert c.baseMethod == "Base"
+assert c.overridedMethod == "Child"
diff --git a/web/assets/bountysource/xored.png b/web/assets/bountysource/xored.png
new file mode 100644
index 000000000..7a07cdd31
--- /dev/null
+++ b/web/assets/bountysource/xored.png
Binary files differdiff --git a/web/news.rst b/web/news.rst
index cd605b760..26f3adfdf 100644
--- a/web/news.rst
+++ b/web/news.rst
@@ -2,6 +2,9 @@
 News
 ====
 
+`2016-06-23 Launching the 2016 Nim community survey <news/2016_06_23_launching_the_2016_nim_community_survey.html>`_
+===================================
+
 `2016-06-11 Version 0.14.2 released <news/2016_06_11_version_0_14_2_released.html>`_
 ===================================
 
diff --git a/web/news/2016_06_23_launching_the_2016_nim_community_survey.rst b/web/news/2016_06_23_launching_the_2016_nim_community_survey.rst
new file mode 100644
index 000000000..0b87577aa
--- /dev/null
+++ b/web/news/2016_06_23_launching_the_2016_nim_community_survey.rst
@@ -0,0 +1,29 @@
+Launching the 2016 Nim Community Survey
+=======================================
+
+.. container:: metadata
+
+  Posted by Dominik Picheta on 23/06/2016
+
+We are proud to announce the official
+`2016 Nim Community Survey <http://goo.gl/forms/XJ3TPsaiIQe5HlTB2>`_! No matter
+whether you use Nim today, have used Nim previously, or never used Nim before;
+we want to know your opinions.
+Your feedback will help the Nim project understand its strengths and
+weaknesses, and to determine development priorities for the future.
+
+It shouldn't take you much longer than 5 to 10 minutes to complete this survey.
+Submissions will be accepted until around the 23rd of July, depending on the
+response rates. If you have any questions or feedback, please don't hesitate
+to get in touch with us via email at survey@nim-lang.org or on the
+`Nim Forum <http://forum.nim-lang.org>`_.
+
+We would appreciate your help in spreading the word about this survey. Share
+the above link on your social network feeds, with your colleagues and in
+other communities.
+
+Thank you to everyone that helped develop and test the survey! Once the
+submission period ends, the results will be shown here and publicised via
+Twitter, the Nim Forum and IRC.
+
+Thanks for your time!
diff --git a/web/ticker.html b/web/ticker.html
index 9e88456a9..e1232d107 100644
--- a/web/ticker.html
+++ b/web/ticker.html
@@ -1,3 +1,8 @@
+<a class="news" href="$1news/2016_06_23_launching_the_2016_nim_community_survey.html">
+  <h4>June 23, 2016</h4>
+  <p>Launching the 2016 Nim community survey!</p>
+</a>
+
 <a class="news" href="$1news/2016_06_11_version_0_14_2_released.html">
   <h4>June 11, 2016</h4>
   <p>Nim version 0.14.2 has been released!</p>
@@ -18,9 +23,4 @@
   <p>Nim version 0.13.0 has been released!</p>
 </a>
 
-<a class="news" href="$1news/2016_01_18_andreas_rumpfs_talk_at_oscon_amsterdam.html">
-  <h4>January 18, 2016</h4>
-  <p>Andreas Rumpf's talk at OSCON Amsterdam</p>
-</a>
-
 <a href="$1news.html" class="blue">See All News...</a>