summary refs log tree commit diff stats
path: root/lib
diff options
context:
space:
mode:
authorAraq <rumpf_a@web.de>2016-07-08 23:20:31 +0200
committerAraq <rumpf_a@web.de>2016-07-08 23:20:31 +0200
commitc48102254a47bfbdbd4deb0d31c4d8cd91ce938b (patch)
tree05c54af182f1772a30d5add9ce3e10cd24f42c0c /lib
parent90f6052955938326481ae96e7e3069c01d74b141 (diff)
parentba273057e31775dfacbd64719641cec7f3b95891 (diff)
downloadNim-c48102254a47bfbdbd4deb0d31c4d8cd91ce938b.tar.gz
merged
Diffstat (limited to 'lib')
-rw-r--r--lib/nimbase.h6
-rw-r--r--lib/pure/asyncdispatch.nim10
-rw-r--r--lib/pure/collections/sets.nim6
-rw-r--r--lib/pure/htmlparser.nim6
-rw-r--r--lib/pure/ioselectors.nim1577
-rw-r--r--lib/pure/ioselects/ioselectors_epoll.nim461
-rw-r--r--lib/pure/ioselects/ioselectors_kqueue.nim439
-rw-r--r--lib/pure/ioselects/ioselectors_poll.nim295
-rw-r--r--lib/pure/ioselects/ioselectors_select.nim416
-rw-r--r--lib/pure/json.nim1
-rw-r--r--lib/pure/os.nim4
-rw-r--r--lib/pure/ospaths.nim2
-rw-r--r--lib/pure/parsexml.nim21
-rw-r--r--lib/pure/pegs.nim4
-rw-r--r--lib/pure/strutils.nim421
-rw-r--r--lib/pure/unicode.nim134
-rw-r--r--lib/system.nim21
-rw-r--r--lib/system/deepcopy.nim2
-rw-r--r--lib/system/nimscript.nim13
-rw-r--r--lib/system/osalloc.nim3
-rw-r--r--lib/system/repr.nim21
-rw-r--r--lib/upcoming/asyncdispatch.nim2154
22 files changed, 4311 insertions, 1706 deletions
diff --git a/lib/nimbase.h b/lib/nimbase.h
index d5a551352..3635ca006 100644
--- a/lib/nimbase.h
+++ b/lib/nimbase.h
@@ -102,7 +102,7 @@ __clang__
        defined __ICL || \
        defined __DMC__ || \
        defined __BORLANDC__ )
-#  define NIM_THREADVAR __declspec(thread) 
+#  define NIM_THREADVAR __declspec(thread)
 /* note that ICC (linux) and Clang are covered by __GNUC__ */
 #elif defined __GNUC__ || \
        defined __SUNPRO_C || \
@@ -449,8 +449,8 @@ static inline void GCGuard (void *ptr) { asm volatile ("" :: "X" (ptr)); }
 
 /* Test to see if Nim and the C compiler agree on the size of a pointer.
    On disagreement, your C compiler will say something like:
-   "error: 'assert_numbits' declared as an array with a negative size" */
-typedef int assert_numbits[sizeof(NI) == sizeof(void*) && NIM_INTBITS == sizeof(NI)*8 ? 1 : -1];
+   "error: 'Nim_and_C_compiler_disagree_on_target_architecture' declared as an array with a negative size" */
+typedef int Nim_and_C_compiler_disagree_on_target_architecture[sizeof(NI) == sizeof(void*) && NIM_INTBITS == sizeof(NI)*8 ? 1 : -1];
 #endif
 
 #ifdef  __cplusplus
diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim
index b61200c72..15d9ab02e 100644
--- a/lib/pure/asyncdispatch.nim
+++ b/lib/pure/asyncdispatch.nim
@@ -1699,8 +1699,10 @@ proc processBody(node, retFutureSym: NimNode,
       else:
         result.add newCall(newIdentNode("complete"), retFutureSym)
     else:
-      result.add newCall(newIdentNode("complete"), retFutureSym,
-        node[0].processBody(retFutureSym, subTypeIsVoid, tryStmt))
+      let x = node[0].processBody(retFutureSym, subTypeIsVoid, tryStmt)
+      if x.kind == nnkYieldStmt: result.add x
+      else:
+        result.add newCall(newIdentNode("complete"), retFutureSym, x)
 
     result.add newNimNode(nnkReturnStmt, node).add(newNilLit())
     return # Don't process the children of this return stmt
@@ -1896,7 +1898,7 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
 
   # -> createCb(retFuture)
   #var cbName = newIdentNode("cb")
-  var procCb = newCall(bindSym"createCb", retFutureSym, iteratorNameSym,
+  var procCb = getAst createCb(retFutureSym, iteratorNameSym,
                        newStrLitNode(prc[0].getName))
   outerProcBody.add procCb
 
@@ -1931,7 +1933,7 @@ macro async*(prc: stmt): stmt {.immediate.} =
       result.add asyncSingleProc(oneProc)
   else:
     result = asyncSingleProc(prc)
-  when defined(nimAsyncDebug):
+  when defined(nimDumpAsync):
     echo repr result
 
 proc recvLine*(socket: AsyncFD): Future[string] {.async.} =
diff --git a/lib/pure/collections/sets.nim b/lib/pure/collections/sets.nim
index e2081e5bf..20e06aaae 100644
--- a/lib/pure/collections/sets.nim
+++ b/lib/pure/collections/sets.nim
@@ -58,7 +58,7 @@ proc isValid*[A](s: HashSet[A]): bool =
   ## initialized. Example:
   ##
   ## .. code-block ::
-  ##   proc savePreferences(options: Set[string]) =
+  ##   proc savePreferences(options: HashSet[string]) =
   ##     assert options.isValid, "Pass an initialized set!"
   ##     # Do stuff here, may crash in release builds!
   result = not s.data.isNil
@@ -72,7 +72,7 @@ proc len*[A](s: HashSet[A]): int =
   ##
   ## .. code-block::
   ##
-  ##   var values: Set[int]
+  ##   var values: HashSet[int]
   ##   assert(not values.isValid)
   ##   assert values.len == 0
   result = s.counter
@@ -338,7 +338,7 @@ proc init*[A](s: var HashSet[A], initialSize=64) =
   ## existing values and calling `excl() <#excl,TSet[A],A>`_ on them. Example:
   ##
   ## .. code-block ::
-  ##   var a: Set[int]
+  ##   var a: HashSet[int]
   ##   a.init(4)
   ##   a.incl(2)
   ##   a.init
diff --git a/lib/pure/htmlparser.nim b/lib/pure/htmlparser.nim
index d620e816e..fd58bed25 100644
--- a/lib/pure/htmlparser.nim
+++ b/lib/pure/htmlparser.nim
@@ -433,7 +433,7 @@ proc htmlTag*(n: XmlNode): HtmlTag =
 proc htmlTag*(s: string): HtmlTag =
   ## converts `s` to a ``HtmlTag``. If `s` is no HTML tag, ``tagUnknown`` is
   ## returned.
-  let s = if allLower(s): s else: s.toLower
+  let s = if allLower(s): s else: toLowerAscii(s)
   result = toHtmlTag(s)
 
 proc entityToUtf8*(entity: string): string =
@@ -513,13 +513,13 @@ proc parse(x: var XmlParser, errors: var seq[string]): XmlNode =
     errors.add(errorMsg(x))
     next(x)
   of xmlElementStart:
-    result = newElement(x.elemName.toLower)
+    result = newElement(toLowerAscii(x.elemName))
     next(x)
     untilElementEnd(x, result, errors)
   of xmlElementEnd:
     errors.add(errorMsg(x, "unexpected ending tag: " & x.elemName))
   of xmlElementOpen:
-    result = newElement(x.elemName.toLower)
+    result = newElement(toLowerAscii(x.elemName))
     next(x)
     result.attrs = newStringTable()
     while true:
diff --git a/lib/pure/ioselectors.nim b/lib/pure/ioselectors.nim
index 034b182ab..a5d5d2c01 100644
--- a/lib/pure/ioselectors.nim
+++ b/lib/pure/ioselectors.nim
@@ -36,14 +36,6 @@ const supportedPlatform = defined(macosx) or defined(freebsd) or
 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
@@ -115,7 +107,7 @@ when defined(nimdoc):
     ## ``data`` application-defined data, which to be passed, when
     ## ``ev`` happens.
 
-  proc newEvent*(): SelectEvent =
+  proc newSelectEvent*(): SelectEvent =
     ## Creates new event ``SelectEvent``.
 
   proc setEvent*(ev: SelectEvent) =
@@ -189,70 +181,20 @@ when defined(nimdoc):
     ##
 
 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
+      SharedArray {.unchecked.}[T] = array[0..100, T]
+
+    proc allocSharedArray[T](nsize: int): ptr SharedArray[T] =
+      result = cast[ptr SharedArray[T]](allocShared0(sizeof(T) * nsize))
+
+    proc deallocSharedArray[T](sa: ptr SharedArray[T]) =
+      deallocShared(cast[pointer](sa))
   type
     Event* {.pure.} = enum
-      Read, Write, Timer, Signal, Process, Vnode, User, Error,
-      flagHandle, flagTimer, flagSignal, flagProcess, flagVnode, flagUser,
-      flagOneshot
+      Read, Write, Timer, Signal, Process, Vnode, User, Error, Oneshot
 
     ReadyKey*[T] = object
       fd* : int
@@ -260,48 +202,29 @@ else:
       data*: T
 
     SelectorKey[T] = object
-      ident : int
-      flags : set[Event]
-      param : int
-      key : ReadyKey[T]
+      ident: int
+      events: 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())
+    import posix
+    proc setNonBlocking(fd: cint) {.inline.} =
+      var x = 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")
+    template setKey(s, pident, pkeyfd, pevents, pparam, pdata) =
+      var skey = addr(s.fds[pident])
+      skey.ident = pident
+      skey.events = pevents
+      skey.param = pparam
+      skey.key.fd = pkeyfd
+      skey.key.data = pdata
 
   when supportedPlatform:
     template blockSignals(newmask: var Sigset, oldmask: var Sigset) =
@@ -319,1446 +242,14 @@ else:
       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
-  #
 
+  when defined(linux):
+    include ioselects/ioselectors_epoll
+  elif bsdPlatform:
+    include ioselects/ioselectors_kqueue
   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
-  #
-
+    include ioselects/ioselectors_select
+  elif defined(solaris):
+    include ioselects/ioselectors_poll # need to replace it with event ports
   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
+    include ioselects/ioselectors_poll
diff --git a/lib/pure/ioselects/ioselectors_epoll.nim b/lib/pure/ioselects/ioselectors_epoll.nim
new file mode 100644
index 000000000..92b2cdc07
--- /dev/null
+++ b/lib/pure/ioselects/ioselectors_epoll.nim
@@ -0,0 +1,461 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2016 Eugene Kabanov
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+# This module implements Linux epoll().
+
+import posix, times
+
+# Maximum number of events that can be returned
+const 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]
+
+  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.
+  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>".}
+proc ulimit(cmd: cint): clong
+     {.importc: "ulimit", header: "<ulimit.h>", varargs.}
+
+when hasThreadSupport:
+  type
+    SelectorImpl[T] = object
+      epollFD : cint
+      maxFD : int
+      fds: ptr SharedArray[SelectorKey[T]]
+      count: int
+    Selector*[T] = ptr SelectorImpl[T]
+else:
+  type
+    SelectorImpl[T] = object
+      epollFD : cint
+      maxFD : int
+      fds: seq[SelectorKey[T]]
+      count: int
+    Selector*[T] = ref SelectorImpl[T]
+type
+  SelectEventImpl = object
+    efd: cint
+  SelectEvent* = ptr SelectEventImpl
+
+proc newSelector*[T](): Selector[T] =
+  var maxFD = int(ulimit(4, 0))
+  doAssert(maxFD > 0)
+
+  var epollFD = epoll_create(MAX_EPOLL_RESULT_EVENTS)
+  if epollFD < 0:
+    raiseOsError(osLastError())
+
+  when hasThreadSupport:
+    result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T])))
+    result.epollFD = epollFD
+    result.maxFD = maxFD
+    result.fds = allocSharedArray[SelectorKey[T]](maxFD)
+  else:
+    result = Selector[T]()
+    result.epollFD = epollFD
+    result.maxFD = maxFD
+    result.fds = newSeq[SelectorKey[T]](maxFD)
+
+proc close*[T](s: Selector[T]) =
+  if posix.close(s.epollFD) != 0:
+    raiseOSError(osLastError())
+  when hasThreadSupport:
+    deallocSharedArray(s.fds)
+    deallocShared(cast[pointer](s))
+
+proc newSelectEvent*(): SelectEvent =
+  let fdci = eventfd(0, 0)
+  if fdci == -1:
+    raiseOSError(osLastError())
+  setNonBlocking(fdci)
+  result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl)))
+  result.efd = fdci
+
+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))
+
+template checkFd(s, f) =
+  if f >= s.maxFD:
+    raise newException(ValueError, "Maximum file descriptors exceeded")
+
+proc registerHandle*[T](s: Selector[T], fd: SocketHandle,
+                        events: set[Event], data: T) =
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  doAssert(s.fds[fdi].ident == 0)
+  s.setKey(fdi, fdi, events, 0, data)
+  if events != {}:
+    var epv = epoll_event(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]) =
+  let maskEvents = {Event.Timer, Event.Signal, Event.Process, Event.Vnode,
+                    Event.User, Event.Oneshot, Event.Error}
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  var pkey = addr(s.fds[fdi])
+  doAssert(pkey.ident != 0)
+  doAssert(pkey.events * maskEvents == {})
+  if pkey.events != events:
+    var epv = epoll_event(events: EPOLLRDHUP)
+    epv.data.u64 = fdi.uint
+
+    if Event.Read in events: epv.events = epv.events or EPOLLIN
+    if Event.Write in events: epv.events = epv.events or EPOLLOUT
+
+    if pkey.events == {}:
+      if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) == -1:
+        raiseOSError(osLastError())
+      inc(s.count)
+    else:
+      if events != {}:
+        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)
+    pkey.events = events
+
+proc unregister*[T](s: Selector[T], fd: int|SocketHandle) =
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  var pkey = addr(s.fds[fdi])
+  doAssert(pkey.ident != 0)
+
+  if pkey.events != {}:
+    if pkey.events * {Event.Read, Event.Write} != {}:
+      var epv = epoll_event()
+      if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
+        raiseOSError(osLastError())
+      dec(s.count)
+    elif Event.Timer in pkey.events:
+      var epv = epoll_event()
+      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.Signal in pkey.events:
+      var epv = epoll_event()
+      if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
+        raiseOSError(osLastError())
+      var nmask, 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.Process in pkey.events:
+      var epv = epoll_event()
+      if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
+        raiseOSError(osLastError())
+      var nmask, omask: Sigset
+      discard sigemptyset(nmask)
+      discard sigemptyset(omask)
+      discard sigaddset(nmask, SIGCHLD)
+      unblockSignals(nmask, omask)
+      discard posix.close(fdi.cint)
+      dec(s.count)
+  pkey.ident = 0
+  pkey.events = {}
+
+proc unregister*[T](s: Selector[T], ev: SelectEvent) =
+  let fdi = int(ev.efd)
+  s.checkFd(fdi)
+  var pkey = addr(s.fds[fdi])
+  doAssert(pkey.ident != 0)
+  doAssert(Event.User in pkey.events)
+  pkey.ident = 0
+  pkey.events = {}
+  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
+  let fdi = timerfd_create(CLOCK_MONOTONIC, 0).int
+  if fdi == -1:
+    raiseOSError(osLastError())
+  setNonBlocking(fdi.cint)
+
+  s.checkFd(fdi)
+  doAssert(s.fds[fdi].ident == 0)
+
+  var events = {Event.Timer}
+  var epv = epoll_event(events: EPOLLIN or EPOLLRDHUP)
+  epv.data.u64 = fdi.uint
+  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
+    incl(events, Event.Oneshot)
+    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())
+  s.setKey(fdi, fdi, events, 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)
+
+  let fdi = signalfd(-1, nmask, 0).int
+  if fdi == -1:
+    raiseOSError(osLastError())
+  setNonBlocking(fdi.cint)
+
+  s.checkFd(fdi)
+  doAssert(s.fds[fdi].ident == 0)
+
+  var epv = epoll_event(events: EPOLLIN or EPOLLRDHUP)
+  epv.data.u64 = fdi.uint
+  if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) == -1:
+    raiseOSError(osLastError())
+  s.setKey(fdi, signal, {Event.Signal}, 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)
+
+  let fdi = signalfd(-1, nmask, 0).int
+  if fdi == -1:
+    raiseOSError(osLastError())
+  setNonBlocking(fdi.cint)
+
+  s.checkFd(fdi)
+  doAssert(s.fds[fdi].ident == 0)
+
+  var epv = epoll_event(events: EPOLLIN or EPOLLRDHUP)
+  epv.data.u64 = fdi.uint
+  epv.events = EPOLLIN or EPOLLRDHUP
+  if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) == -1:
+    raiseOSError(osLastError())
+  s.setKey(fdi, pid, {Event.Process, Event.Oneshot}, pid, data)
+  inc(s.count)
+  result = fdi
+
+proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
+  let fdi = int(ev.efd)
+  doAssert(s.fds[fdi].ident == 0)
+  s.setKey(fdi, fdi, {Event.User}, 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 flush*[T](s: Selector[T]) =
+  discard
+
+proc selectInto*[T](s: Selector[T], timeout: int,
+                    results: var openarray[ReadyKey[T]]): int =
+  var
+    resTable: array[MAX_EPOLL_RESULT_EVENTS, epoll_event]
+    maxres = MAX_EPOLL_RESULT_EVENTS
+    events: set[Event] = {}
+    i, k: int
+
+  if maxres > len(results):
+    maxres = len(results)
+
+  let count = epoll_wait(s.epollFD, addr(resTable[0]), maxres.cint,
+                         timeout.cint)
+  if count < 0:
+    result = 0
+    let err = osLastError()
+    if cint(err) != EINTR:
+      raiseOSError(err)
+  elif count == 0:
+    result = 0
+  else:
+    i = 0
+    k = 0
+    while i < count:
+      let fdi = int(resTable[i].data.u64)
+      let pevents = resTable[i].events
+      var skey = addr(s.fds[fdi])
+      doAssert(skey.ident != 0)
+      events = {}
+
+      if (pevents and EPOLLERR) != 0 or (pevents and EPOLLHUP) != 0:
+        events.incl(Event.Error)
+      if (pevents and EPOLLOUT) != 0:
+        events.incl(Event.Write)
+      if (pevents and EPOLLIN) != 0:
+        if Event.Read in skey.events:
+          events.incl(Event.Read)
+        elif Event.Timer in skey.events:
+          var data: uint64 = 0
+          if posix.read(fdi.cint, addr data, sizeof(uint64)) != sizeof(uint64):
+            raiseOSError(osLastError())
+          events = {Event.Timer}
+        elif Event.Signal in skey.events:
+          var data = SignalFdInfo()
+          if posix.read(fdi.cint, addr data,
+                        sizeof(SignalFdInfo)) != sizeof(SignalFdInfo):
+            raiseOsError(osLastError())
+          events = {Event.Signal}
+        elif Event.Process in skey.events:
+          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 = {Event.Process}
+          else:
+            inc(i)
+            continue
+        elif Event.User in skey.events:
+          var data: uint = 0
+          if posix.read(fdi.cint, addr data, sizeof(uint)) != sizeof(uint):
+            let err = osLastError()
+            if err == OSErrorCode(EAGAIN):
+              inc(i)
+              continue
+            else:
+              raiseOSError(err)
+          events = {Event.User}
+
+      skey.key.events = events
+      results[k] = skey.key
+      inc(k)
+
+      if Event.Oneshot in skey.events:
+        var epv = epoll_event()
+        if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1:
+          raiseOSError(osLastError())
+        discard posix.close(fdi.cint)
+        skey.ident = 0
+        skey.events = {}
+        dec(s.count)
+      inc(i)
+    result = k
+
+proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] =
+  result = newSeq[ReadyKey[T]](MAX_EPOLL_RESULT_EVENTS)
+  let count = selectInto(s, timeout, result)
+  result.setLen(count)
+
+template isEmpty*[T](s: Selector[T]): bool =
+  (s.count == 0)
+
+template withData*[T](s: Selector[T], fd: SocketHandle, value,
+                        body: untyped) =
+  mixin checkFd
+  let fdi = int(fd)
+  s.checkFd(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) =
+  mixin checkFd
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  if s.fds[fdi].ident != 0:
+    var value = addr(s.fds[fdi].key.data)
+    body1
+  else:
+    body2
diff --git a/lib/pure/ioselects/ioselectors_kqueue.nim b/lib/pure/ioselects/ioselectors_kqueue.nim
new file mode 100644
index 000000000..29a201863
--- /dev/null
+++ b/lib/pure/ioselects/ioselectors_kqueue.nim
@@ -0,0 +1,439 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2016 Eugene Kabanov
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+#  This module implements BSD kqueue().
+
+import posix, times, kqueue
+
+const
+  # Maximum number of cached changes.
+  MAX_KQUEUE_CHANGE_EVENTS = 64
+  # Maximum number of events that can be returned.
+  MAX_KQUEUE_RESULT_EVENTS = 64
+
+when defined(macosx) or defined(freebsd):
+  when defined(macosx):
+    const MAX_DESCRIPTORS_ID = 29 # KERN_MAXFILESPERPROC (MacOS)
+  else:
+    const MAX_DESCRIPTORS_ID = 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 MAX_DESCRIPTORS_ID = 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>"""}
+
+when hasThreadSupport:
+  type
+    SelectorImpl[T] = object
+      kqFD : cint
+      maxFD : int
+      changesTable: array[MAX_KQUEUE_CHANGE_EVENTS, KEvent]
+      changesCount: int
+      fds: ptr SharedArray[SelectorKey[T]]
+      count: int
+      changesLock: Lock
+    Selector*[T] = ptr SelectorImpl[T]
+else:
+  type
+    SelectorImpl[T] = object
+      kqFD : cint
+      maxFD : int
+      changesTable: array[MAX_KQUEUE_CHANGE_EVENTS, KEvent]
+      changesCount: int
+      fds: seq[SelectorKey[T]]
+      count: int
+    Selector*[T] = ref 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 = 0.cint
+  var size = sizeof(cint)
+  var namearr = [1.cint, MAX_DESCRIPTORS_ID.cint]
+  # Obtain maximum number of file descriptors for process
+  if sysctl(addr(namearr[0]), 2, cast[pointer](addr maxFD), addr size,
+            nil, 0) != 0:
+    raiseOsError(osLastError())
+
+  var kqFD = kqueue()
+  if kqFD < 0:
+    raiseOsError(osLastError())
+
+  when hasThreadSupport:
+    result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T])))
+    result.kqFD = kqFD
+    result.maxFD = maxFD.int
+    result.fds = allocSharedArray[SelectorKey[T]](maxFD)
+    initLock(result.changesLock)
+  else:
+    result = Selector[T]()
+    result.kqFD = kqFD
+    result.maxFD = maxFD.int
+    result.fds = newSeq[SelectorKey[T]](maxFD)
+
+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))
+
+proc newSelectEvent*(): 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: uint64 = 1
+  if posix.write(ev.wfd, addr data, sizeof(uint64)) != sizeof(uint64):
+    raiseOSError(osLastError())
+
+proc close*(ev: SelectEvent) =
+  discard posix.close(cint(ev.rfd))
+  discard posix.close(cint(ev.wfd))
+  deallocShared(cast[pointer](ev))
+
+template checkFd(s, f) =
+  if f >= s.maxFD:
+    raise newException(ValueError, "Maximum file descriptors exceeded")
+
+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) =
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  doAssert(s.fds[fdi].ident == 0)
+  s.setKey(fdi, fdi, 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]) =
+  let maskEvents = {Event.Timer, Event.Signal, Event.Process, Event.Vnode,
+                    Event.User, Event.Oneshot, Event.Error}
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  var pkey = addr(s.fds[fdi])
+  doAssert(pkey.ident != 0)
+  doAssert(pkey.events * maskEvents == {})
+
+  if pkey.events != events:
+    if (Event.Read in pkey.events) and (Event.Read notin events):
+      modifyKQueue(s, fdi.uint, EVFILT_READ, EV_DELETE, 0, 0, nil)
+      dec(s.count)
+    if (Event.Write in pkey.events) and (Event.Write notin events):
+      modifyKQueue(s, fdi.uint, EVFILT_WRITE, EV_DELETE, 0, 0, nil)
+      dec(s.count)
+    if (Event.Read notin pkey.events) and (Event.Read in events):
+      modifyKQueue(s, fdi.uint, EVFILT_READ, EV_ADD, 0, 0, nil)
+      inc(s.count)
+    if (Event.Write notin pkey.events) and (Event.Write in events):
+      modifyKQueue(s, fdi.uint, EVFILT_WRITE, EV_ADD, 0, 0, nil)
+      inc(s.count)
+    pkey.events = events
+
+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.checkFd(fdi)
+  doAssert(s.fds[fdi].ident == 0)
+
+  let events = if oneshot: {Event.Timer, Event.Oneshot} else: {Event.Timer}
+  let flags: cushort = if oneshot: EV_ONESHOT or EV_ADD else: EV_ADD
+
+  s.setKey(fdi, fdi, events, 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, flags, 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.checkFd(fdi)
+  doAssert(s.fds[fdi].ident == 0)
+
+  s.setKey(fdi, signal, {Event.Signal}, signal, data)
+  var nmask, 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.checkFd(fdi)
+  doAssert(s.fds[fdi].ident == 0)
+
+  var kflags: cushort = EV_ONESHOT or EV_ADD
+  setKey(s, fdi, pid, {Event.Process, Event.Oneshot}, pid, data)
+  modifyKQueue(s, pid.uint, EVFILT_PROC, kflags, NOTE_EXIT, 0,
+               cast[pointer](fdi))
+  inc(s.count)
+  result = fdi
+
+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.User}, 0, data)
+  modifyKQueue(s, fdi.uint, EVFILT_READ, EV_ADD, 0, 0, nil)
+  inc(s.count)
+
+proc unregister*[T](s: Selector[T], fd: int|SocketHandle) =
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  var pkey = addr(s.fds[fdi])
+  doAssert(pkey.ident != 0)
+
+  if pkey.events != {}:
+    if pkey.events * {Event.Read, Event.Write} != {}:
+      if Event.Read in pkey.events:
+        modifyKQueue(s, fdi.uint, EVFILT_READ, EV_DELETE, 0, 0, nil)
+        dec(s.count)
+      if Event.Write in pkey.events:
+        modifyKQueue(s, fdi.uint, EVFILT_WRITE, EV_DELETE, 0, 0, nil)
+        dec(s.count)
+    elif Event.Timer in pkey.events:
+      discard posix.close(cint(pkey.key.fd))
+      modifyKQueue(s, fdi.uint, EVFILT_TIMER, EV_DELETE, 0, 0, nil)
+      dec(s.count)
+    elif Event.Signal in pkey.events:
+      var nmask, omask: Sigset
+      var signal = cint(pkey.param)
+      discard sigemptyset(nmask)
+      discard sigemptyset(omask)
+      discard sigaddset(nmask, signal)
+      unblockSignals(nmask, omask)
+      posix.signal(signal, SIG_DFL)
+      discard posix.close(cint(pkey.key.fd))
+      modifyKQueue(s, fdi.uint, EVFILT_SIGNAL, EV_DELETE, 0, 0, nil)
+      dec(s.count)
+    elif Event.Process in pkey.events:
+      discard posix.close(cint(pkey.key.fd))
+      modifyKQueue(s, fdi.uint, EVFILT_PROC, EV_DELETE, 0, 0, nil)
+      dec(s.count)
+    elif Event.User in pkey.events:
+      modifyKQueue(s, fdi.uint, EVFILT_READ, EV_DELETE, 0, 0, nil)
+      dec(s.count)
+  pkey.ident = 0
+  pkey.events = {}
+
+proc unregister*[T](s: Selector[T], ev: SelectEvent) =
+  let fdi = int(ev.rfd)
+  s.checkFd(fdi)
+  var pkey = addr(s.fds[fdi])
+  doAssert(pkey.ident != 0)
+  doAssert(Event.User in pkey.events)
+  pkey.ident = 0
+  pkey.events = {}
+  modifyKQueue(s, fdi.uint, EVFILT_READ, EV_DELETE, 0, 0, nil)
+  dec(s.count)
+
+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
+
+proc selectInto*[T](s: Selector[T], timeout: int,
+                    results: var openarray[ReadyKey[T]]): int =
+  var
+    tv: Timespec
+    resTable: array[MAX_KQUEUE_RESULT_EVENTS, KEvent]
+    ptv = addr tv
+    maxres = MAX_KQUEUE_RESULT_EVENTS
+
+  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
+
+  if maxres > len(results):
+    maxres = len(results)
+
+  var count = 0
+  s.withChangeLock():
+    count = kevent(s.kqFD, addr(s.changesTable[0]), cint(s.changesCount),
+                   addr(resTable[0]), cint(maxres), ptv)
+    s.changesCount = 0
+
+  if count < 0:
+    result = 0
+    let err = osLastError()
+    if cint(err) != EINTR:
+      raiseOSError(err)
+  elif count == 0:
+    result = 0
+  else:
+    var i = 0
+    var k = 0
+    var pkey: ptr SelectorKey[T]
+    while i < count:
+      let kevent = addr(resTable[i])
+      if (kevent.flags and EV_ERROR) == 0:
+        case kevent.filter:
+        of EVFILT_READ:
+          pkey = addr(s.fds[kevent.ident.int])
+          pkey.key.events = {Event.Read}
+          if Event.User in pkey.events:
+            var data: uint64 = 0
+            if posix.read(kevent.ident.cint, addr data,
+                          sizeof(uint64)) != sizeof(uint64):
+              let err = osLastError()
+              if err == OSErrorCode(EAGAIN):
+                # someone already consumed event data
+                inc(i)
+                continue
+              else:
+                raiseOSError(osLastError())
+            pkey.key.events = {Event.User}
+        of EVFILT_WRITE:
+          pkey = addr(s.fds[kevent.ident.int])
+          pkey.key.events = {Event.Write}
+        of EVFILT_TIMER:
+          pkey = addr(s.fds[kevent.ident.int])
+          if Event.Oneshot in pkey.events:
+            if posix.close(cint(pkey.ident)) == -1:
+              raiseOSError(osLastError())
+            pkey.ident = 0
+            pkey.events = {}
+            dec(s.count)
+          pkey.key.events = {Event.Timer}
+        of EVFILT_VNODE:
+          pkey = addr(s.fds[kevent.ident.int])
+          pkey.key.events = {Event.Vnode}
+        of EVFILT_SIGNAL:
+          pkey = addr(s.fds[cast[int](kevent.udata)])
+          pkey.key.events = {Event.Signal}
+        of EVFILT_PROC:
+          pkey = addr(s.fds[cast[int](kevent.udata)])
+          if posix.close(cint(pkey.ident)) == -1:
+            raiseOSError(osLastError())
+          pkey.ident = 0
+          pkey.events = {}
+          dec(s.count)
+          pkey.key.events = {Event.Process}
+        else:
+          raise newException(ValueError, "Unsupported kqueue filter in queue")
+
+        if (kevent.flags and EV_EOF) != 0:
+          pkey.key.events.incl(Event.Error)
+
+        results[k] = pkey.key
+        inc(k)
+      inc(i)
+    result = k
+
+proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] =
+  result = newSeq[ReadyKey[T]](MAX_KQUEUE_RESULT_EVENTS)
+  let count = selectInto(s, timeout, result)
+  result.setLen(count)
+
+template isEmpty*[T](s: Selector[T]): bool =
+  (s.count == 0)
+
+template withData*[T](s: Selector[T], fd: SocketHandle, value,
+                        body: untyped) =
+  mixin checkFd
+  let fdi = int(fd)
+  s.checkFd(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) =
+  mixin checkFd
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  if s.fds[fdi].ident != 0:
+    var value = addr(s.fds[fdi].key.data)
+    body1
+  else:
+    body2
diff --git a/lib/pure/ioselects/ioselectors_poll.nim b/lib/pure/ioselects/ioselectors_poll.nim
new file mode 100644
index 000000000..d2a0a1273
--- /dev/null
+++ b/lib/pure/ioselects/ioselectors_poll.nim
@@ -0,0 +1,295 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2016 Eugene Kabanov
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+# This module implements Posix poll().
+
+import posix, times
+
+# Maximum number of events that can be returned
+const MAX_POLL_RESULT_EVENTS = 64
+
+when hasThreadSupport:
+  type
+    SelectorImpl[T] = object
+      maxFD : int
+      pollcnt: int
+      fds: ptr SharedArray[SelectorKey[T]]
+      pollfds: ptr SharedArray[TPollFd]
+      count: int
+      lock: Lock
+    Selector*[T] = ptr SelectorImpl[T]
+else:
+  type
+    SelectorImpl[T] = object
+      maxFD : int
+      pollcnt: int
+      fds: seq[SelectorKey[T]]
+      pollfds: seq[TPollFd]
+      count: int
+    Selector*[T] = ref SelectorImpl[T]
+
+type
+  SelectEventImpl = object
+    rfd: cint
+    wfd: cint
+  SelectEvent* = ptr SelectEventImpl
+
+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>".}
+
+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 a = rlimit()
+  if getrlimit(RLIMIT_NOFILE, a) != 0:
+    raiseOsError(osLastError())
+  var maxFD = int(a.rlim_max)
+
+  when hasThreadSupport:
+    result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T])))
+    result.maxFD = maxFD
+    result.fds = allocSharedArray[SelectorKey[T]](maxFD)
+    result.pollfds = allocSharedArray[TPollFd](maxFD)
+    initLock(result.lock)
+  else:
+    result = Selector[T]()
+    result.maxFD = maxFD
+    result.fds = newSeq[SelectorKey[T]](maxFD)
+    result.pollfds = newSeq[TPollFd](maxFD)
+
+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)
+
+template checkFd(s, f) =
+  if f >= s.maxFD:
+    raise newException(ValueError, "Maximum file descriptors exceeded")
+
+proc registerHandle*[T](s: Selector[T], fd: SocketHandle,
+                        events: set[Event], data: T) =
+  var fdi = int(fd)
+  s.checkFd(fdi)
+  doAssert(s.fds[fdi].ident == 0)
+  s.setKey(fdi, fdi, events, 0, data)
+  if events != {}: s.pollAdd(fdi.cint, events)
+
+proc updateHandle*[T](s: Selector[T], fd: SocketHandle,
+                      events: set[Event]) =
+  let maskEvents = {Event.Timer, Event.Signal, Event.Process, Event.Vnode,
+                    Event.User, Event.Oneshot, Event.Error}
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  var pkey = addr(s.fds[fdi])
+  doAssert(pkey.ident != 0)
+  doAssert(pkey.events * maskEvents == {})
+
+  if pkey.events != events:
+    if pkey.events == {}:
+      s.pollAdd(fd.cint, events)
+    else:
+      if events != {}:
+        s.pollUpdate(fd.cint, events)
+      else:
+        s.pollRemove(fd.cint)
+    pkey.events = events
+
+proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
+  var fdi = int(ev.rfd)
+  doAssert(s.fds[fdi].ident == 0)
+  var events = {Event.User}
+  setKey(s, fdi, fdi, events, 0, data)
+  events.incl(Event.Read)
+  s.pollAdd(fdi.cint, events)
+
+proc flush*[T](s: Selector[T]) = discard
+
+proc unregister*[T](s: Selector[T], fd: int|SocketHandle) =
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  var pkey = addr(s.fds[fdi])
+  doAssert(pkey.ident != 0)
+  pkey.ident = 0
+  pkey.events = {}
+  s.pollRemove(fdi.cint)
+
+proc unregister*[T](s: Selector[T], ev: SelectEvent) =
+  let fdi = int(ev.rfd)
+  s.checkFd(fdi)
+  var pkey = addr(s.fds[fdi])
+  doAssert(pkey.ident != 0)
+  doAssert(Event.User in pkey.events)
+  pkey.ident = 0
+  pkey.events = {}
+  s.pollRemove(fdi.cint)
+
+proc newSelectEvent*(): 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: uint64 = 1
+  if posix.write(ev.wfd, addr data, sizeof(uint64)) != sizeof(uint64):
+    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 maxres = MAX_POLL_RESULT_EVENTS
+  if maxres > len(results):
+    maxres = len(results)
+
+  s.withPollLock():
+    let count = posix.poll(addr(s.pollfds[0]), Tnfds(s.pollcnt), timeout)
+    if count < 0:
+      result = 0
+      let err = osLastError()
+      if err.cint == EINTR:
+        discard
+      else:
+        raiseOSError(osLastError())
+    elif count == 0:
+      result = 0
+    else:
+      var i = 0
+      var k = 0
+      var rindex = 0
+      while (i < s.pollcnt) and (k < count) and (rindex < maxres):
+        let revents = s.pollfds[i].revents
+        if revents != 0:
+          let fd = s.pollfds[i].fd
+          var skey = addr(s.fds[fd])
+          skey.key.events = {}
+
+          if (revents and POLLIN) != 0:
+            skey.key.events.incl(Event.Read)
+            if Event.User in skey.events:
+              var data: uint64 = 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
+              skey.key.events = {Event.User}
+          if (revents and POLLOUT) != 0:
+            skey.key.events.incl(Event.Write)
+          if (revents and POLLERR) != 0 or (revents and POLLHUP) != 0 or
+             (revents and POLLNVAL) != 0:
+            skey.key.events.incl(Event.Error)
+          results[rindex] = skey.key
+          s.pollfds[i].revents = 0
+          inc(rindex)
+          inc(k)
+        inc(i)
+      result = k
+
+proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] =
+  result = newSeq[ReadyKey[T]](MAX_POLL_RESULT_EVENTS)
+  let count = selectInto(s, timeout, result)
+  result.setLen(count)
+
+template isEmpty*[T](s: Selector[T]): bool =
+  (s.count == 0)
+
+template withData*[T](s: Selector[T], fd: SocketHandle, value,
+                        body: untyped) =
+  mixin checkFd
+  let fdi = int(fd)
+  s.checkFd(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) =
+  mixin checkFd
+  let fdi = int(fd)
+  s.checkFd(fdi)
+  if s.fds[fdi].ident != 0:
+    var value = addr(s.fds[fdi].key.data)
+    body1
+  else:
+    body2
diff --git a/lib/pure/ioselects/ioselectors_select.nim b/lib/pure/ioselects/ioselectors_select.nim
new file mode 100644
index 000000000..f8099f9a0
--- /dev/null
+++ b/lib/pure/ioselects/ioselectors_select.nim
@@ -0,0 +1,416 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2016 Eugene Kabanov
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+# This module implements Posix and Windows select().
+
+import times, nativesockets
+
+when defined(windows):
+  import winlean
+  when defined(gcc):
+    {.passL: "-lws2_32".}
+  elif defined(vcc):
+    {.passL: "ws2_32.lib".}
+  const platformHeaders = """#include <winsock2.h>
+                             #include <windows.h>"""
+  const EAGAIN = WSAEWOULDBLOCK
+else:
+  const platformHeaders = """#include <sys/select.h>
+                             #include <sys/time.h>
+                             #include <sys/types.h>
+                             #include <unistd.h>"""
+type
+  Fdset {.importc: "fd_set", header: platformHeaders, pure, final.} = object
+var
+  FD_SETSIZE {.importc: "FD_SETSIZE", header: platformHeaders.}: cint
+
+proc IOFD_SET(fd: SocketHandle, fdset: ptr Fdset)
+     {.cdecl, importc: "FD_SET", header: platformHeaders, inline.}
+proc IOFD_CLR(fd: SocketHandle, fdset: ptr Fdset)
+     {.cdecl, importc: "FD_CLR", header: platformHeaders, inline.}
+proc IOFD_ZERO(fdset: ptr Fdset)
+     {.cdecl, importc: "FD_ZERO", header: platformHeaders, inline.}
+
+when defined(windows):
+  proc IOFD_ISSET(fd: SocketHandle, fdset: ptr Fdset): cint
+       {.stdcall, importc: "FD_ISSET", header: platformHeaders, inline.}
+  proc ioselect(nfds: cint, readFds, writeFds, exceptFds: ptr Fdset,
+                timeout: ptr Timeval): cint
+       {.stdcall, importc: "select", header: platformHeaders.}
+else:
+  proc IOFD_ISSET(fd: SocketHandle, fdset: ptr Fdset): cint
+       {.cdecl, importc: "FD_ISSET", header: platformHeaders, inline.}
+  proc ioselect(nfds: cint, readFds, writeFds, exceptFds: ptr Fdset,
+                timeout: ptr Timeval): cint
+       {.cdecl, importc: "select", header: platformHeaders.}
+
+when hasThreadSupport:
+  type
+    SelectorImpl[T] = object
+      rSet: FdSet
+      wSet: FdSet
+      eSet: FdSet
+      maxFD: int
+      fds: ptr SharedArray[SelectorKey[T]]
+      count: int
+      lock: Lock
+    Selector*[T] = ptr SelectorImpl[T]
+else:
+  type
+    SelectorImpl[T] = object
+      rSet: FdSet
+      wSet: FdSet
+      eSet: FdSet
+      maxFD: int
+      fds: seq[SelectorKey[T]]
+      count: int
+    Selector*[T] = ref SelectorImpl[T]
+
+type
+  SelectEventImpl = object
+    rsock: SocketHandle
+    wsock: SocketHandle
+  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] =
+  when hasThreadSupport:
+    result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T])))
+    result.fds = allocSharedArray[SelectorKey[T]](FD_SETSIZE)
+    initLock result.lock
+  else:
+    result = Selector[T]()
+    result.fds = newSeq[SelectorKey[T]](FD_SETSIZE)
+
+  IOFD_ZERO(addr result.rSet)
+  IOFD_ZERO(addr result.wSet)
+  IOFD_ZERO(addr result.eSet)
+
+proc close*[T](s: Selector[T]) =
+  when hasThreadSupport:
+    deallocSharedArray(s.fds)
+    deallocShared(cast[pointer](s))
+
+when defined(windows):
+  proc newSelectEvent*(): SelectEvent =
+    var ssock = newNativeSocket()
+    var wsock = newNativeSocket()
+    var rsock: SocketHandle = INVALID_SOCKET
+    var saddr = Sockaddr_in()
+
+    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
+
+  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))
+
+else:
+  proc newSelectEvent*(): 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.rsock = SocketHandle(fds[0])
+    result.wsock = SocketHandle(fds[1])
+
+  proc setEvent*(ev: SelectEvent) =
+    var data: uint64 = 1
+    if posix.write(cint(ev.wsock), addr data, sizeof(uint64)) != sizeof(uint64):
+      raiseOSError(osLastError())
+
+  proc close*(ev: SelectEvent) =
+    discard posix.close(cint(ev.rsock))
+    discard posix.close(cint(ev.wsock))
+    deallocShared(cast[pointer](ev))
+
+proc setKey[T](s: Selector[T], fd: SocketHandle, events: set[Event], data: T) =
+  var i = 0
+  let fdi = int(fd)
+  while i < FD_SETSIZE:
+    if s.fds[i].ident == 0:
+      var pkey = addr(s.fds[i])
+      pkey.ident = fdi
+      pkey.events = events
+      pkey.key.fd = fd.int
+      pkey.key.events = {}
+      pkey.key.data = data
+      break
+    inc(i)
+  if i == FD_SETSIZE:
+    raise newException(ValueError, "Maximum numbers of fds exceeded")
+
+proc getKey[T](s: Selector[T], fd: SocketHandle): ptr SelectorKey[T] =
+  var i = 0
+  let fdi = int(fd)
+  while i < FD_SETSIZE:
+    if s.fds[i].ident == fdi:
+      result = addr(s.fds[i])
+      break
+    inc(i)
+  doAssert(i < FD_SETSIZE, "Descriptor not registered in queue")
+
+proc delKey[T](s: Selector[T], fd: SocketHandle) =
+  var i = 0
+  while i < FD_SETSIZE:
+    if s.fds[i].ident == fd.int:
+      s.fds[i].ident = 0
+      s.fds[i].events = {}
+      break
+    inc(i)
+  doAssert(i < FD_SETSIZE, "Descriptor not registered in queue")
+
+proc registerHandle*[T](s: Selector[T], fd: SocketHandle,
+                        events: set[Event], data: T) =
+  when not defined(windows):
+    let fdi = int(fd)
+  s.withSelectLock():
+    s.setKey(fd, events, data)
+    when not defined(windows):
+      if fdi > s.maxFD: s.maxFD = fdi
+    if Event.Read in events:
+      IOFD_SET(fd, addr s.rSet)
+      inc(s.count)
+    if Event.Write in events:
+      IOFD_SET(fd, addr s.wSet)
+      IOFD_SET(fd, addr s.eSet)
+      inc(s.count)
+
+proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
+  when not defined(windows):
+    let fdi = int(ev.rsock)
+  s.withSelectLock():
+    s.setKey(ev.rsock, {Event.User}, data)
+    when not defined(windows):
+      if fdi > s.maxFD: s.maxFD = fdi
+    IOFD_SET(ev.rsock, addr s.rSet)
+    inc(s.count)
+
+proc updateHandle*[T](s: Selector[T], fd: SocketHandle,
+                      events: set[Event]) =
+  let maskEvents = {Event.Timer, Event.Signal, Event.Process, Event.Vnode,
+                    Event.User, Event.Oneshot, Event.Error}
+  s.withSelectLock():
+    var pkey = s.getKey(fd)
+    doAssert(pkey.events * maskEvents == {})
+    if pkey.events != events:
+      if (Event.Read in pkey.events) and (Event.Read notin events):
+        IOFD_CLR(fd, addr s.rSet)
+        dec(s.count)
+      if (Event.Write in pkey.events) and (Event.Write notin events):
+        IOFD_CLR(fd, addr s.wSet)
+        IOFD_CLR(fd, addr s.eSet)
+        dec(s.count)
+      if (Event.Read notin pkey.events) and (Event.Read in events):
+        IOFD_SET(fd, addr s.rSet)
+        inc(s.count)
+      if (Event.Write notin pkey.events) and (Event.Write in events):
+        IOFD_SET(fd, addr s.wSet)
+        IOFD_SET(fd, addr s.eSet)
+        inc(s.count)
+      pkey.events = events
+
+proc unregister*[T](s: Selector[T], fd: SocketHandle) =
+  s.withSelectLock():
+    var pkey = s.getKey(fd)
+    if Event.Read in pkey.events:
+      IOFD_CLR(fd, addr s.rSet)
+      dec(s.count)
+    if Event.Write in pkey.events:
+      IOFD_CLR(fd, addr s.wSet)
+      IOFD_CLR(fd, addr s.eSet)
+      dec(s.count)
+    s.delKey(fd)
+
+proc unregister*[T](s: Selector[T], ev: SelectEvent) =
+  let fd = ev.rsock
+  s.withSelectLock():
+    IOFD_CLR(fd, addr s.rSet)
+    dec(s.count)
+    s.delKey(fd)
+
+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: FdSet
+
+  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 = ioselect(cint(s.maxFD) + 1, addr(rset), addr(wset),
+                       addr(eset), ptv)
+  if count < 0:
+    result = 0
+    when defined(windows):
+      raiseOSError(osLastError())
+    else:
+      let err = osLastError()
+      if cint(err) != EINTR:
+        raiseOSError(err)
+  elif count == 0:
+    result = 0
+  else:
+    var rindex = 0
+    var i = 0
+    var k = 0
+
+    while (i < FD_SETSIZE) and (k < count):
+      if s.fds[i].ident != 0:
+        var flag = false
+        var pkey = addr(s.fds[i])
+        pkey.key.events = {}
+        let fd = SocketHandle(pkey.ident)
+        if IOFD_ISSET(fd, addr rset) != 0:
+          if Event.User in pkey.events:
+            var data: uint64 = 0
+            if recv(fd, cast[pointer](addr(data)),
+                    sizeof(uint64).cint, 0) != sizeof(uint64):
+              let err = osLastError()
+              if cint(err) != EAGAIN:
+                raiseOSError(err)
+              else:
+                inc(i)
+                inc(k)
+                continue
+            else:
+              flag = true
+              pkey.key.events = {Event.User}
+          else:
+            flag = true
+            pkey.key.events = {Event.Read}
+        if IOFD_ISSET(fd, addr wset) != 0:
+          pkey.key.events.incl(Event.Write)
+          if IOFD_ISSET(fd, addr eset) != 0:
+            pkey.key.events.incl(Event.Error)
+          flag = true
+        if flag:
+          results[rindex] = pkey.key
+          inc(rindex)
+          inc(k)
+      inc(i)
+    result = rindex
+
+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)
+
+proc flush*[T](s: Selector[T]) = discard
+
+template isEmpty*[T](s: Selector[T]): bool =
+  (s.count == 0)
+
+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
+
+template withData*[T](s: Selector[T], fd: SocketHandle, value,
+                      body: untyped) =
+  mixin withSelectLock
+  s.withSelectLock():
+    var value: ptr T
+    let fdi = int(fd)
+    var i = 0
+    while i < FD_SETSIZE:
+      if s.fds[i].ident == fdi:
+        value = addr(s.fds[i].key.data)
+        break
+      inc(i)
+    if i != FD_SETSIZE:
+      body
+
+template withData*[T](s: Selector[T], fd: SocketHandle, value,
+                      body1, body2: untyped) =
+  mixin withSelectLock
+  s.withSelectLock():
+    var value: ptr T
+    let fdi = int(fd)
+    var i = 0
+    while i < FD_SETSIZE:
+      if s.fds[i].ident == fdi:
+        value = addr(s.fds[i].key.data)
+        break
+      inc(i)
+    if i != FD_SETSIZE:
+      body1
+    else:
+      body2
diff --git a/lib/pure/json.nim b/lib/pure/json.nim
index b4eecdf88..5198f5e00 100644
--- a/lib/pure/json.nim
+++ b/lib/pure/json.nim
@@ -712,6 +712,7 @@ proc `%`*(b: bool): JsonNode =
 
 proc `%`*(keyVals: openArray[tuple[key: string, val: JsonNode]]): JsonNode =
   ## Generic constructor for JSON data. Creates a new `JObject JsonNode`
+  if keyvals.len == 0: return newJArray()
   result = newJObject()
   for key, val in items(keyVals): result.fields[key] = val
 
diff --git a/lib/pure/os.nim b/lib/pure/os.nim
index 52ba110a9..d543ec9a0 100644
--- a/lib/pure/os.nim
+++ b/lib/pure/os.nim
@@ -66,13 +66,13 @@ proc osErrorMsg*(): string {.rtl, extern: "nos$1", deprecated.} =
     if err != 0'i32:
       when useWinUnicode:
         var msgbuf: WideCString
-        if formatMessageW(0x00000100 or 0x00001000 or 0x00000200,
+        if formatMessageW(0x00000100 or 0x00001000 or 0x00000200 or 0x000000FF,
                           nil, err, 0, addr(msgbuf), 0, nil) != 0'i32:
           result = $msgbuf
           if msgbuf != nil: localFree(cast[pointer](msgbuf))
       else:
         var msgbuf: cstring
-        if formatMessageA(0x00000100 or 0x00001000 or 0x00000200,
+        if formatMessageA(0x00000100 or 0x00001000 or 0x00000200 or 0x000000FF,
                           nil, err, 0, addr(msgbuf), 0, nil) != 0'i32:
           result = $msgbuf
           if msgbuf != nil: localFree(msgbuf)
diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim
index 9fc816f2f..65588529e 100644
--- a/lib/pure/ospaths.nim
+++ b/lib/pure/ospaths.nim
@@ -10,7 +10,7 @@
 # Included by the ``os`` module but a module in its own right for NimScript
 # support.
 
-when isMainModule:
+when not declared(os):
   {.pragma: rtl.}
   import strutils
 
diff --git a/lib/pure/parsexml.nim b/lib/pure/parsexml.nim
index 06daa3782..d16a55302 100644
--- a/lib/pure/parsexml.nim
+++ b/lib/pure/parsexml.nim
@@ -142,6 +142,9 @@ proc kind*(my: XmlParser): XmlEventKind {.inline.} =
 template charData*(my: XmlParser): string =
   ## returns the character data for the events: ``xmlCharData``,
   ## ``xmlWhitespace``, ``xmlComment``, ``xmlCData``, ``xmlSpecial``
+  ## Raises an assertion in debug mode if ``my.kind`` is not one 
+  ## of those events. In release mode, this will not trigger an error
+  ## but the value returned will not be valid. 
   assert(my.kind in {xmlCharData, xmlWhitespace, xmlComment, xmlCData,
                      xmlSpecial})
   my.a
@@ -149,31 +152,49 @@ template charData*(my: XmlParser): string =
 template elementName*(my: XmlParser): string =
   ## returns the element name for the events: ``xmlElementStart``,
   ## ``xmlElementEnd``, ``xmlElementOpen``
+  ## Raises an assertion in debug mode if ``my.kind`` is not one 
+  ## of those events. In release mode, this will not trigger an error
+  ## but the value returned will not be valid. 
   assert(my.kind in {xmlElementStart, xmlElementEnd, xmlElementOpen})
   my.a
 
 template entityName*(my: XmlParser): string =
   ## returns the entity name for the event: ``xmlEntity``
+  ## Raises an assertion in debug mode if ``my.kind`` is not 
+  ## ``xmlEntity``. In release mode, this will not trigger an error
+  ## but the value returned will not be valid. 
   assert(my.kind == xmlEntity)
   my.a
 
 template attrKey*(my: XmlParser): string =
   ## returns the attribute key for the event ``xmlAttribute``
+  ## Raises an assertion in debug mode if ``my.kind`` is not 
+  ## ``xmlAttribute``. In release mode, this will not trigger an error
+  ## but the value returned will not be valid. 
   assert(my.kind == xmlAttribute)
   my.a
 
 template attrValue*(my: XmlParser): string =
   ## returns the attribute value for the event ``xmlAttribute``
+  ## Raises an assertion in debug mode if ``my.kind`` is not 
+  ## ``xmlAttribute``. In release mode, this will not trigger an error
+  ## but the value returned will not be valid. 
   assert(my.kind == xmlAttribute)
   my.b
 
 template piName*(my: XmlParser): string =
   ## returns the processing instruction name for the event ``xmlPI``
+  ## Raises an assertion in debug mode if ``my.kind`` is not 
+  ## ``xmlPI``. In release mode, this will not trigger an error
+  ## but the value returned will not be valid. 
   assert(my.kind == xmlPI)
   my.a
 
 template piRest*(my: XmlParser): string =
   ## returns the rest of the processing instruction for the event ``xmlPI``
+  ## Raises an assertion in debug mode if ``my.kind`` is not 
+  ## ``xmlPI``. In release mode, this will not trigger an error
+  ## but the value returned will not be valid. 
   assert(my.kind == xmlPI)
   my.b
 
diff --git a/lib/pure/pegs.nim b/lib/pure/pegs.nim
index 7e1f50266..5c978a2f8 100644
--- a/lib/pure/pegs.nim
+++ b/lib/pure/pegs.nim
@@ -1841,8 +1841,8 @@ when isMainModule:
       result.add ", "
 
     result.add case n:
-      of 2: c[0].toLower & ": '" & c[1] & "'"
-      of 1: c[0].toLower & ": ''"
+      of 2: toLowerAscii(c[0]) & ": '" & c[1] & "'"
+      of 1: toLowerAscii(c[0]) & ": ''"
       else: ""
 
   assert("Var1=key1;var2=Key2;   VAR3".
diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim
index 7d1b1a3d9..9b6cf45c5 100644
--- a/lib/pure/strutils.nim
+++ b/lib/pure/strutils.nim
@@ -26,6 +26,12 @@ include "system/inclrtl"
 
 {.pop.}
 
+# Support old split with set[char]
+when defined(nimOldSplit):
+  {.pragma: deprecatedSplit, deprecated.}
+else:
+  {.pragma: deprecatedSplit.}
+
 type
   CharSet* {.deprecated.} = set[char] # for compatibility with Nim
 {.deprecated: [TCharSet: CharSet].}
@@ -64,8 +70,8 @@ const
     ##   doAssert "01234".find(invalid) == -1
     ##   doAssert "01A34".find(invalid) == 2
 
-proc isAlpha*(c: char): bool {.noSideEffect, procvar,
-  rtl, extern: "nsuIsAlphaChar".}=
+proc isAlphaAscii*(c: char): bool {.noSideEffect, procvar,
+  rtl, extern: "nsuIsAlphaAsciiChar".}=
   ## Checks whether or not `c` is alphabetical.
   ##
   ## This checks a-z, A-Z ASCII characters only.
@@ -85,27 +91,27 @@ proc isDigit*(c: char): bool {.noSideEffect, procvar,
   ## This checks 0-9 ASCII characters only.
   return c in Digits
 
-proc isSpace*(c: char): bool {.noSideEffect, procvar,
-  rtl, extern: "nsuIsSpaceChar".}=
+proc isSpaceAscii*(c: char): bool {.noSideEffect, procvar,
+  rtl, extern: "nsuIsSpaceAsciiChar".}=
   ## Checks whether or not `c` is a whitespace character.
   return c in Whitespace
 
-proc isLower*(c: char): bool {.noSideEffect, procvar,
-  rtl, extern: "nsuIsLowerChar".}=
+proc isLowerAscii*(c: char): bool {.noSideEffect, procvar,
+  rtl, extern: "nsuIsLowerAsciiChar".}=
   ## Checks whether or not `c` is a lower case character.
   ##
   ## This checks ASCII characters only.
   return c in {'a'..'z'}
 
-proc isUpper*(c: char): bool {.noSideEffect, procvar,
-  rtl, extern: "nsuIsUpperChar".}=
+proc isUpperAscii*(c: char): bool {.noSideEffect, procvar,
+  rtl, extern: "nsuIsUpperAsciiChar".}=
   ## Checks whether or not `c` is an upper case character.
   ##
   ## This checks ASCII characters only.
   return c in {'A'..'Z'}
 
-proc isAlpha*(s: string): bool {.noSideEffect, procvar,
-  rtl, extern: "nsuIsAlphaStr".}=
+proc isAlphaAscii*(s: string): bool {.noSideEffect, procvar,
+  rtl, extern: "nsuIsAlphaAsciiStr".}=
   ## Checks whether or not `s` is alphabetical.
   ##
   ## This checks a-z, A-Z ASCII characters only.
@@ -117,7 +123,7 @@ proc isAlpha*(s: string): bool {.noSideEffect, procvar,
 
   result = true
   for c in s:
-    result = c.isAlpha() and result
+    result = c.isAlphaAscii() and result
 
 proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar,
   rtl, extern: "nsuIsAlphaNumericStr".}=
@@ -149,8 +155,8 @@ proc isDigit*(s: string): bool {.noSideEffect, procvar,
   for c in s:
     result = c.isDigit() and result
 
-proc isSpace*(s: string): bool {.noSideEffect, procvar,
-  rtl, extern: "nsuIsSpaceStr".}=
+proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar,
+  rtl, extern: "nsuIsSpaceAsciiStr".}=
   ## Checks whether or not `s` is completely whitespace.
   ##
   ## Returns true if all characters in `s` are whitespace
@@ -160,11 +166,11 @@ proc isSpace*(s: string): bool {.noSideEffect, procvar,
 
   result = true
   for c in s:
-    if not c.isSpace():
+    if not c.isSpaceAscii():
       return false
 
-proc isLower*(s: string): bool {.noSideEffect, procvar,
-  rtl, extern: "nsuIsLowerStr".}=
+proc isLowerAscii*(s: string): bool {.noSideEffect, procvar,
+  rtl, extern: "nsuIsLowerAsciiStr".}=
   ## Checks whether or not `s` contains all lower case characters.
   ##
   ## This checks ASCII characters only.
@@ -175,10 +181,10 @@ proc isLower*(s: string): bool {.noSideEffect, procvar,
 
   result = true
   for c in s:
-    result = c.isLower() and result
+    result = c.isLowerAscii() and result
 
-proc isUpper*(s: string): bool {.noSideEffect, procvar,
-  rtl, extern: "nsuIsUpperStr".}=
+proc isUpperAscii*(s: string): bool {.noSideEffect, procvar,
+  rtl, extern: "nsuIsUpperAsciiStr".}=
   ## Checks whether or not `s` contains all upper case characters.
   ##
   ## This checks ASCII characters only.
@@ -189,10 +195,10 @@ proc isUpper*(s: string): bool {.noSideEffect, procvar,
 
   result = true
   for c in s:
-    result = c.isUpper() and result
+    result = c.isUpperAscii() and result
 
-proc toLower*(c: char): char {.noSideEffect, procvar,
-  rtl, extern: "nsuToLowerChar".} =
+proc toLowerAscii*(c: char): char {.noSideEffect, procvar,
+  rtl, extern: "nsuToLowerAsciiChar".} =
   ## Converts `c` into lower case.
   ##
   ## This works only for the letters ``A-Z``. See `unicode.toLower
@@ -203,8 +209,8 @@ proc toLower*(c: char): char {.noSideEffect, procvar,
   else:
     result = c
 
-proc toLower*(s: string): string {.noSideEffect, procvar,
-  rtl, extern: "nsuToLowerStr".} =
+proc toLowerAscii*(s: string): string {.noSideEffect, procvar,
+  rtl, extern: "nsuToLowerAsciiStr".} =
   ## Converts `s` into lower case.
   ##
   ## This works only for the letters ``A-Z``. See `unicode.toLower
@@ -212,10 +218,10 @@ proc toLower*(s: string): string {.noSideEffect, procvar,
   ## character.
   result = newString(len(s))
   for i in 0..len(s) - 1:
-    result[i] = toLower(s[i])
+    result[i] = toLowerAscii(s[i])
 
-proc toUpper*(c: char): char {.noSideEffect, procvar,
-  rtl, extern: "nsuToUpperChar".} =
+proc toUpperAscii*(c: char): char {.noSideEffect, procvar,
+  rtl, extern: "nsuToUpperAsciiChar".} =
   ## Converts `c` into upper case.
   ##
   ## This works only for the letters ``A-Z``.  See `unicode.toUpper
@@ -226,8 +232,8 @@ proc toUpper*(c: char): char {.noSideEffect, procvar,
   else:
     result = c
 
-proc toUpper*(s: string): string {.noSideEffect, procvar,
-  rtl, extern: "nsuToUpperStr".} =
+proc toUpperAscii*(s: string): string {.noSideEffect, procvar,
+  rtl, extern: "nsuToUpperAsciiStr".} =
   ## Converts `s` into upper case.
   ##
   ## This works only for the letters ``A-Z``.  See `unicode.toUpper
@@ -235,14 +241,145 @@ proc toUpper*(s: string): string {.noSideEffect, procvar,
   ## character.
   result = newString(len(s))
   for i in 0..len(s) - 1:
-    result[i] = toUpper(s[i])
+    result[i] = toUpperAscii(s[i])
+
+proc capitalizeAscii*(s: string): string {.noSideEffect, procvar,
+  rtl, extern: "nsuCapitalizeAscii".} =
+  ## Converts the first character of `s` into upper case.
+  ##
+  ## This works only for the letters ``A-Z``.
+  result = toUpperAscii(s[0]) & substr(s, 1)
+
+proc isSpace*(c: char): bool {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuIsSpaceChar".}=
+  ## Checks whether or not `c` is a whitespace character.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``isSpaceAscii`` instead.
+  isSpaceAscii(c)
+
+proc isLower*(c: char): bool {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuIsLowerChar".}=
+  ## Checks whether or not `c` is a lower case character.
+  ##
+  ## This checks ASCII characters only.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``isLowerAscii`` instead.
+  isLowerAscii(c)
+
+proc isUpper*(c: char): bool {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuIsUpperChar".}=
+  ## Checks whether or not `c` is an upper case character.
+  ##
+  ## This checks ASCII characters only.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``isUpperAscii`` instead.
+  isUpperAscii(c)
+
+proc isAlpha*(c: char): bool {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuIsAlphaChar".}=
+  ## Checks whether or not `c` is alphabetical.
+  ##
+  ## This checks a-z, A-Z ASCII characters only.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``isAlphaAscii`` instead.
+  isAlphaAscii(c)
+
+proc isAlpha*(s: string): bool {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuIsAlphaStr".}=
+  ## Checks whether or not `s` is alphabetical.
+  ##
+  ## This checks a-z, A-Z ASCII characters only.
+  ## Returns true if all characters in `s` are
+  ## alphabetic and there is at least one character
+  ## in `s`.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``isAlphaAscii`` instead.
+  isAlphaAscii(s)
+
+proc isSpace*(s: string): bool {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuIsSpaceStr".}=
+  ## Checks whether or not `s` is completely whitespace.
+  ##
+  ## Returns true if all characters in `s` are whitespace
+  ## characters and there is at least one character in `s`.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``isSpaceAscii`` instead.
+  isSpaceAscii(s)
+
+proc isLower*(s: string): bool {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuIsLowerStr".}=
+  ## Checks whether or not `s` contains all lower case characters.
+  ##
+  ## This checks ASCII characters only.
+  ## Returns true if all characters in `s` are lower case
+  ## and there is at least one character  in `s`.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``isLowerAscii`` instead.
+  isLowerAscii(s)
+
+proc isUpper*(s: string): bool {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuIsUpperStr".}=
+  ## Checks whether or not `s` contains all upper case characters.
+  ##
+  ## This checks ASCII characters only.
+  ## Returns true if all characters in `s` are upper case
+  ## and there is at least one character in `s`.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``isUpperAscii`` instead.
+  isUpperAscii(s)
+
+proc toLower*(c: char): char {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuToLowerChar".} =
+  ## Converts `c` into lower case.
+  ##
+  ## This works only for the letters ``A-Z``. See `unicode.toLower
+  ## <unicode.html#toLower>`_ for a version that works for any Unicode
+  ## character.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``toLowerAscii`` instead.
+  toLowerAscii(c)
+
+proc toLower*(s: string): string {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuToLowerStr".} =
+  ## Converts `s` into lower case.
+  ##
+  ## This works only for the letters ``A-Z``. See `unicode.toLower
+  ## <unicode.html#toLower>`_ for a version that works for any Unicode
+  ## character.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``toLowerAscii`` instead.
+  toLowerAscii(s)
+
+proc toUpper*(c: char): char {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuToUpperChar".} =
+  ## Converts `c` into upper case.
+  ##
+  ## This works only for the letters ``A-Z``.  See `unicode.toUpper
+  ## <unicode.html#toUpper>`_ for a version that works for any Unicode
+  ## character.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``toUpperAscii`` instead.
+  toUpperAscii(c)
+
+proc toUpper*(s: string): string {.noSideEffect, procvar,
+  rtl, deprecated, extern: "nsuToUpperStr".} =
+  ## Converts `s` into upper case.
+  ##
+  ## This works only for the letters ``A-Z``.  See `unicode.toUpper
+  ## <unicode.html#toUpper>`_ for a version that works for any Unicode
+  ## character.
+  ##
+  ## **Deprecated since version 0.15.0**: use ``toUpperAscii`` instead.
+  toUpperAscii(s)
 
 proc capitalize*(s: string): string {.noSideEffect, procvar,
-  rtl, extern: "nsuCapitalize".} =
+  rtl, deprecated, extern: "nsuCapitalize".} =
   ## Converts the first character of `s` into upper case.
   ##
   ## This works only for the letters ``A-Z``.
-  result = toUpper(s[0]) & substr(s, 1)
+  ##
+  ## **Deprecated since version 0.15.0**: use ``capitalizeAscii`` instead.
+  capitalizeAscii(s)
 
 proc normalize*(s: string): string {.noSideEffect, procvar,
   rtl, extern: "nsuNormalize".} =
@@ -271,7 +408,7 @@ proc cmpIgnoreCase*(a, b: string): int {.noSideEffect,
   var i = 0
   var m = min(a.len, b.len)
   while i < m:
-    result = ord(toLower(a[i])) - ord(toLower(b[i]))
+    result = ord(toLowerAscii(a[i])) - ord(toLowerAscii(b[i]))
     if result != 0: return
     inc(i)
   result = a.len - b.len
@@ -292,8 +429,8 @@ proc cmpIgnoreStyle*(a, b: string): int {.noSideEffect,
   while true:
     while a[i] == '_': inc(i)
     while b[j] == '_': inc(j) # BUGFIX: typo
-    var aa = toLower(a[i])
-    var bb = toLower(b[j])
+    var aa = toLowerAscii(a[i])
+    var bb = toLowerAscii(b[j])
     result = ord(aa) - ord(bb)
     if result != 0 or aa == '\0': break
     inc(i)
@@ -341,17 +478,63 @@ proc isNilOrWhitespace*(s: string): bool {.noSideEffect, procvar, rtl, extern: "
     if not c.isSpace():
       return false
 
+proc substrEq(s: string, pos: int, substr: string): bool =
+  var i = 0
+  var length = substr.len
+  while i < length and s[pos+i] == substr[i]:
+    inc i
+
+  return i == length
+
+# --------- Private templates for different split separators -----------
+
+template stringHasSep(s: string, index: int, seps: set[char]): bool =
+  s[index] in seps
+
+template stringHasSep(s: string, index: int, sep: char): bool =
+  s[index] == sep
+
+template stringHasSep(s: string, index: int, sep: string): bool =
+  s.substrEq(index, sep)
+
+template splitCommon(s, sep, maxsplit, sepLen) =
+  ## Common code for split procedures
+  var last = 0
+  var splits = maxsplit
+
+  if len(s) > 0:
+    while last <= len(s):
+      var first = last
+      while last < len(s) and not stringHasSep(s, last, sep):
+        inc(last)
+      if splits == 0: last = len(s)
+      yield substr(s, first, last-1)
+      if splits == 0: break
+      dec(splits)
+      inc(last, sepLen)
+
+template oldSplit(s, seps, maxsplit) =
+  var last = 0
+  var splits = maxsplit
+  assert(not ('\0' in seps))
+  while last < len(s):
+    while s[last] in seps: inc(last)
+    var first = last
+    while last < len(s) and s[last] notin seps: inc(last)
+    if first <= last-1:
+      if splits == 0: last = len(s)
+      yield substr(s, first, last-1)
+      if splits == 0: break
+      dec(splits)
+
 iterator split*(s: string, seps: set[char] = Whitespace,
                 maxsplit: int = -1): string =
   ## Splits the string `s` into substrings using a group of separators.
   ##
-  ## Substrings are separated by a substring containing only `seps`. Note
-  ## that whole sequences of characters found in ``seps`` will be counted as
-  ## a single split point and leading/trailing separators will be ignored.
-  ## The following example:
+  ## Substrings are separated by a substring containing only `seps`.
   ##
   ## .. code-block:: nim
-  ##   for word in split("  this is an  example  "):
+  ##   for word in split("this\lis an\texample"):
   ##     writeLine(stdout, word)
   ##
   ## ...generates this output:
@@ -365,7 +548,7 @@ iterator split*(s: string, seps: set[char] = Whitespace,
   ## And the following code:
   ##
   ## .. code-block:: nim
-  ##   for word in split(";;this;is;an;;example;;;", {';'}):
+  ##   for word in split("this:is;an$example", {';', ':', '$'}):
   ##     writeLine(stdout, word)
   ##
   ## ...produces the same output as the first example. The code:
@@ -386,26 +569,26 @@ iterator split*(s: string, seps: set[char] = Whitespace,
   ##   "08"
   ##   "08.398990"
   ##
-  var last = 0
-  var splits = maxsplit
-  assert(not ('\0' in seps))
-  while last < len(s):
-    while s[last] in seps: inc(last)
-    var first = last
-    while last < len(s) and s[last] notin seps: inc(last) # BUGFIX!
-    if first <= last-1:
-      if splits == 0: last = len(s)
-      yield substr(s, first, last-1)
-      if splits == 0: break
-      dec(splits)
+  when defined(nimOldSplit):
+    oldSplit(s, seps, maxsplit)
+  else:
+    splitCommon(s, seps, maxsplit, 1)
+
+iterator splitWhitespace*(s: string): string =
+  ## Splits at whitespace.
+  oldSplit(s, Whitespace, -1)
+
+proc splitWhitespace*(s: string): seq[string] {.noSideEffect,
+  rtl, extern: "nsuSplitWhitespace".} =
+  ## The same as the `splitWhitespace <#splitWhitespace.i,string>`_
+  ## iterator, but is a proc that returns a sequence of substrings.
+  accumulateResult(splitWhitespace(s))
 
 iterator split*(s: string, sep: char, maxsplit: int = -1): string =
   ## Splits the string `s` into substrings using a single separator.
   ##
   ## Substrings are separated by the character `sep`.
-  ## Unlike the version of the iterator which accepts a set of separator
-  ## characters, this proc will not coalesce groups of the
-  ## separator, returning a string for each found character. The code:
+  ## The code:
   ##
   ## .. code-block:: nim
   ##   for word in split(";;this;is;an;;example;;;", ';'):
@@ -425,56 +608,27 @@ iterator split*(s: string, sep: char, maxsplit: int = -1): string =
   ##   ""
   ##   ""
   ##
-  var last = 0
-  var splits = maxsplit
-  assert('\0' != sep)
-  if len(s) > 0:
-    # `<=` is correct here for the edge cases!
-    while last <= len(s):
-      var first = last
-      while last < len(s) and s[last] != sep: inc(last)
-      if splits == 0: last = len(s)
-      yield substr(s, first, last-1)
-      if splits == 0: break
-      dec(splits)
-      inc(last)
-
-proc substrEq(s: string, pos: int, substr: string): bool =
-  var i = 0
-  var length = substr.len
-  while i < length and s[pos+i] == substr[i]:
-    inc i
-
-  return i == length
+  splitCommon(s, sep, maxsplit, 1)
 
 iterator split*(s: string, sep: string, maxsplit: int = -1): string =
   ## Splits the string `s` into substrings using a string separator.
   ##
   ## Substrings are separated by the string `sep`.
-  var last = 0
-  var splits = maxsplit
-
-  if len(s) > 0:
-    while last <= len(s):
-      var first = last
-      while last < len(s) and not s.substrEq(last, sep):
-        inc(last)
-      if splits == 0: last = len(s)
-      yield substr(s, first, last-1)
-      if splits == 0: break
-      dec(splits)
-      inc(last, sep.len)
-
-# --------- Private templates for different rsplit separators -----------
-
-template stringHasSep(s: string, index: int, seps: set[char]): bool =
-  s[index] in seps
-
-template stringHasSep(s: string, index: int, sep: char): bool =
-  s[index] == sep
+  ## The code:
+  ##
+  ## .. code-block:: nim
+  ##   for word in split("thisDATAisDATAcorrupted", "DATA"):
+  ##     writeLine(stdout, word)
+  ##
+  ## Results in:
+  ##
+  ## .. code-block::
+  ##   "this"
+  ##   "is"
+  ##   "corrupted"
+  ##
 
-template stringHasSep(s: string, index: int, sep: string): bool =
-  s.substrEq(index, sep)
+  splitCommon(s, sep, maxsplit, sep.len)
 
 template rsplitCommon(s, sep, maxsplit, sepLen) =
   ## Common code for rsplit functions
@@ -2138,13 +2292,13 @@ when isMainModule:
 
   doAssert "  foo\n  bar".indent(4, "Q") == "QQQQ  foo\nQQQQ  bar"
 
-  doAssert isAlpha('r')
-  doAssert isAlpha('A')
-  doAssert(not isAlpha('$'))
+  doAssert isAlphaAscii('r')
+  doAssert isAlphaAscii('A')
+  doAssert(not isAlphaAscii('$'))
 
-  doAssert isAlpha("Rasp")
-  doAssert isAlpha("Args")
-  doAssert(not isAlpha("$Tomato"))
+  doAssert isAlphaAscii("Rasp")
+  doAssert isAlphaAscii("Args")
+  doAssert(not isAlphaAscii("$Tomato"))
 
   doAssert isAlphaNumeric('3')
   doAssert isAlphaNumeric('R')
@@ -2163,13 +2317,13 @@ when isMainModule:
   doAssert(not isDigit("12.33"))
   doAssert(not isDigit("A45b"))
 
-  doAssert isSpace('\t')
-  doAssert isSpace('\l')
-  doAssert(not isSpace('A'))
+  doAssert isSpaceAscii('\t')
+  doAssert isSpaceAscii('\l')
+  doAssert(not isSpaceAscii('A'))
 
-  doAssert isSpace("\t\l \v\r\f")
-  doAssert isSpace("       ")
-  doAssert(not isSpace("ABc   \td"))
+  doAssert isSpaceAscii("\t\l \v\r\f")
+  doAssert isSpaceAscii("       ")
+  doAssert(not isSpaceAscii("ABc   \td"))
 
   doAssert(isNilOrEmpty(""))
   doAssert(isNilOrEmpty(nil))
@@ -2182,24 +2336,24 @@ when isMainModule:
   doAssert(isNilOrWhitespace("\t\l \v\r\f"))
   doAssert(not isNilOrWhitespace("ABc   \td"))
 
-  doAssert isLower('a')
-  doAssert isLower('z')
-  doAssert(not isLower('A'))
-  doAssert(not isLower('5'))
-  doAssert(not isLower('&'))
+  doAssert isLowerAscii('a')
+  doAssert isLowerAscii('z')
+  doAssert(not isLowerAscii('A'))
+  doAssert(not isLowerAscii('5'))
+  doAssert(not isLowerAscii('&'))
 
-  doAssert isLower("abcd")
-  doAssert(not isLower("abCD"))
-  doAssert(not isLower("33aa"))
+  doAssert isLowerAscii("abcd")
+  doAssert(not isLowerAscii("abCD"))
+  doAssert(not isLowerAscii("33aa"))
 
-  doAssert isUpper('A')
-  doAssert(not isUpper('b'))
-  doAssert(not isUpper('5'))
-  doAssert(not isUpper('%'))
+  doAssert isUpperAscii('A')
+  doAssert(not isUpperAscii('b'))
+  doAssert(not isUpperAscii('5'))
+  doAssert(not isUpperAscii('%'))
 
-  doAssert isUpper("ABC")
-  doAssert(not isUpper("AAcc"))
-  doAssert(not isUpper("A#$"))
+  doAssert isUpperAscii("ABC")
+  doAssert(not isUpperAscii("AAcc"))
+  doAssert(not isUpperAscii("A#$"))
 
   doAssert rsplit("foo bar", seps=Whitespace) == @["foo", "bar"]
   doAssert rsplit(" foo bar", seps=Whitespace, maxsplit=1) == @[" foo", "bar"]
@@ -2244,11 +2398,14 @@ bar
     bar
   """.unindent() == "foo\nfoo\nbar\n"
 
-  let s = " this   is     an example   "
-  doAssert s.split() == @["this", "is", "an", "example"]
-  doAssert s.split(maxsplit=4) == @["this", "is", "an", "example"]
-  doAssert s.split(' ', maxsplit=4) == @["", "this", "", "", "is     an example   "]
-  doAssert s.split(" ", maxsplit=4) == @["", "this", "", "", "is     an example   "]
+  let s = " this is an example  "
+  let s2 = ":this;is;an:example;;"
+
+  doAssert s.split() == @["", "this", "is", "an", "example", "", ""]
+  doAssert s2.split(seps={':', ';'}) == @["", "this", "is", "an", "example", "", ""]
+  doAssert s.split(maxsplit=4) == @["", "this", "is", "an", "example  "]
+  doAssert s.split(' ', maxsplit=1) == @["", "this is an example  "]
+  doAssert s.split(" ", maxsplit=4) == @["", "this", "is", "an", "example  "]
 
   block: # formatEng tests
     doAssert formatEng(0, 2, trim=false) == "0.00"
diff --git a/lib/pure/unicode.nim b/lib/pure/unicode.nim
index ac25dccef..b5383c5d3 100644
--- a/lib/pure/unicode.nim
+++ b/lib/pure/unicode.nim
@@ -1369,6 +1369,64 @@ proc isCombining*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} =
     (c >= 0x20d0 and c <= 0x20ff) or
     (c >= 0xfe20 and c <= 0xfe2f))
 
+template runeCheck(s, runeProc) =
+  ## Common code for rune.isLower, rune.isUpper, etc
+  result = if len(s) == 0: false else: true
+
+  var
+    i = 0
+    rune: Rune
+
+  while i < len(s) and result:
+    fastRuneAt(s, i, rune, doInc=true)
+    result = runeProc(rune) and result
+
+proc isUpper*(s: string): bool {.noSideEffect, procvar,
+  rtl, extern: "nuc$1Str".} =
+  ## Returns true iff `s` contains all upper case unicode characters.
+  runeCheck(s, isUpper)
+
+proc isLower*(s: string): bool {.noSideEffect, procvar,
+  rtl, extern: "nuc$1Str".} =
+  ## Returns true iff `s` contains all lower case unicode characters.
+  runeCheck(s, isLower)
+
+proc isAlpha*(s: string): bool {.noSideEffect, procvar,
+  rtl, extern: "nuc$1Str".} =
+  ## Returns true iff `s` contains all alphabetic unicode characters.
+  runeCheck(s, isAlpha)
+
+proc isSpace*(s: string): bool {.noSideEffect, procvar,
+  rtl, extern: "nuc$1Str".} =
+  ## Returns true iff `s` contains all whitespace unicode characters.
+  runeCheck(s, isWhiteSpace)
+
+template convertRune(s, runeProc) =
+  ## Convert runes in `s` using `runeProc` as the converter.
+  result = newString(len(s))
+
+  var
+    i = 0
+    lastIndex = 0
+    rune: Rune
+
+  while i < len(s):
+    lastIndex = i
+    fastRuneAt(s, i, rune, doInc=true)
+    rune = runeProc(rune)
+
+    rune.fastToUTF8Copy(result, lastIndex)
+
+proc toUpper*(s: string): string {.noSideEffect, procvar,
+  rtl, extern: "nuc$1Str".} =
+  ## Converts `s` into upper-case unicode characters.
+  convertRune(s, toUpper)
+
+proc toLower*(s: string): string {.noSideEffect, procvar,
+  rtl, extern: "nuc$1Str".} =
+  ## Converts `s` into lower-case unicode characters.
+  convertRune(s, toLower)
+
 proc swapCase*(s: string): string {.noSideEffect, procvar,
   rtl, extern: "nuc$1".} =
   ## Swaps the case of unicode characters in `s`
@@ -1395,6 +1453,20 @@ proc swapCase*(s: string): string {.noSideEffect, procvar,
 
     rune.fastToUTF8Copy(result, lastIndex)
 
+proc capitalize*(s: string): string {.noSideEffect, procvar,
+  rtl, extern: "nuc$1".} =
+  ## Converts the first character of `s` into an upper-case unicode character.
+  if len(s) == 0:
+    return s
+
+  var
+    rune: Rune
+    i = 0
+
+  fastRuneAt(s, i, rune, doInc=true)
+
+  result = $toUpper(rune) & substr(s, i)
+
 proc translate*(s: string, replacements: proc(key: string): string): string {.
   rtl, extern: "nuc$1".} =
   ## Translates words in a string using the `replacements` proc to substitute
@@ -1618,6 +1690,10 @@ when isMainModule:
   doAssert title("αlpha βeta γamma") == "Αlpha Βeta Γamma"
   doAssert title("") == ""
 
+  doAssert capitalize("βeta") == "Βeta"
+  doAssert capitalize("foo") == "Foo"
+  doAssert capitalize("") == ""
+
   doAssert isTitle("Foo")
   doAssert(not isTitle("Foo bar"))
   doAssert(not isTitle("αlpha Βeta"))
@@ -1630,6 +1706,64 @@ when isMainModule:
   doAssert swapCase("a✓B") == "A✓b"
   doAssert swapCase("") == ""
 
+  doAssert isAlpha("r")
+  doAssert isAlpha("α")
+  doAssert(not isAlpha("$"))
+  doAssert(not isAlpha(""))
+
+  doAssert isAlpha("Î’eta")
+  doAssert isAlpha("Args")
+  doAssert(not isAlpha("$Foo✓"))
+
+  doAssert isSpace("\t")
+  doAssert isSpace("\l")
+  doAssert(not isSpace("Î’"))
+  doAssert(not isSpace("Î’eta"))
+
+  doAssert isSpace("\t\l \v\r\f")
+  doAssert isSpace("       ")
+  doAssert(not isSpace(""))
+  doAssert(not isSpace("ΑΓc   \td"))
+
+  doAssert isLower("a")
+  doAssert isLower("γ")
+  doAssert(not isLower("Γ"))
+  doAssert(not isLower("4"))
+  doAssert(not isLower(""))
+
+  doAssert isLower("abcdγ")
+  doAssert(not isLower("abCDΓ"))
+  doAssert(not isLower("33aaΓ"))
+
+  doAssert isUpper("Γ")
+  doAssert(not isUpper("b"))
+  doAssert(not isUpper("α"))
+  doAssert(not isUpper("✓"))
+  doAssert(not isUpper(""))
+
+  doAssert isUpper("ΑΒΓ")
+  doAssert(not isUpper("AAccβ"))
+  doAssert(not isUpper("A#$β"))
+
+  doAssert toUpper("Γ") == "Γ"
+  doAssert toUpper("b") == "B"
+  doAssert toUpper("α") == "Α"
+  doAssert toUpper("✓") == "✓"
+  doAssert toUpper("") == ""
+
+  doAssert toUpper("ΑΒΓ") == "ΑΒΓ"
+  doAssert toUpper("AAccβ") == "AACCΒ"
+  doAssert toUpper("A✓$β") == "A✓$Β"
+
+  doAssert toLower("a") == "a"
+  doAssert toLower("γ") == "γ"
+  doAssert toLower("Γ") == "γ"
+  doAssert toLower("4") == "4"
+  doAssert toLower("") == ""
+
+  doAssert toLower("abcdγ") == "abcdγ"
+  doAssert toLower("abCDΓ") == "abcdγ"
+  doAssert toLower("33aaΓ") == "33aaγ"
 
   doAssert reversed("Reverse this!") == "!siht esreveR"
   doAssert reversed("先秦兩漢") == "漢兩秦先"
diff --git a/lib/system.nim b/lib/system.nim
index 5a84f4a52..6a4265e5a 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -3633,6 +3633,27 @@ proc `==` *(x, y: cstring): bool {.magic: "EqCString", noSideEffect,
   elif x.isNil or y.isNil: result = false
   else: result = strcmp(x, y) == 0
 
+template closureScope*(body: untyped): stmt =
+  ## Useful when creating a closure in a loop to capture local loop variables by
+  ## their current iteration values. Example:
+  ##
+  ## .. code-block:: nim
+  ##   var myClosure : proc()
+  ##   # without closureScope:
+  ##   for i in 0 .. 5:
+  ##     let j = i
+  ##     if j == 3:
+  ##       myClosure = proc() = echo j
+  ##   myClosure() # outputs 5. `j` is changed after closure creation
+  ##   # with closureScope:
+  ##   for i in 0 .. 5:
+  ##     closureScope: # Everything in this scope is locked after closure creation
+  ##       let j = i
+  ##       if j == 3:
+  ##         myClosure = proc() = echo j
+  ##   myClosure() # outputs 3
+  (proc() = body)()
+
 {.pop.} #{.push warning[GcMem]: off, warning[Uninit]: off.}
 
 when defined(nimconfig):
diff --git a/lib/system/deepcopy.nim b/lib/system/deepcopy.nim
index 6e512c9b1..5445a067c 100644
--- a/lib/system/deepcopy.nim
+++ b/lib/system/deepcopy.nim
@@ -124,7 +124,9 @@ proc genericDeepCopyAux(dest, src: pointer, mt: PNimType) =
     copyMem(dest, src, mt.size)
 
 proc genericDeepCopy(dest, src: pointer, mt: PNimType) {.compilerProc.} =
+  GC_disable()
   genericDeepCopyAux(dest, src, mt)
+  GC_enable()
 
 proc genericSeqDeepCopy(dest, src: pointer, mt: PNimType) {.compilerProc.} =
   # also invoked for 'string'
diff --git a/lib/system/nimscript.nim b/lib/system/nimscript.nim
index d587d772f..db6d72d7b 100644
--- a/lib/system/nimscript.nim
+++ b/lib/system/nimscript.nim
@@ -39,6 +39,9 @@ proc getCurrentDir(): string = builtin
 proc rawExec(cmd: string): int {.tags: [ExecIOEffect], raises: [OSError].} =
   builtin
 
+proc warningImpl(arg, orig: string) = discard
+proc hintImpl(arg, orig: string) = discard
+
 proc paramStr*(i: int): string =
   ## Retrieves the ``i``'th command line parameter.
   builtin
@@ -52,6 +55,16 @@ proc switch*(key: string, val="") =
   ## example ``switch("checks", "on")``.
   builtin
 
+proc warning*(name: string; val: bool) =
+  ## Disables or enables a specific warning.
+  let v = if val: "on" else: "off"
+  warningImpl(name & "]:" & v, "warning[" & name & "]:" & v)
+
+proc hint*(name: string; val: bool) =
+  ## Disables or enables a specific hint.
+  let v = if val: "on" else: "off"
+  hintImpl(name & "]:" & v, "hint[" & name & "]:" & v)
+
 proc getCommand*(): string =
   ## Gets the Nim command that the compiler has been invoked with, for example
   ## "c", "js", "build", "help".
diff --git a/lib/system/osalloc.nim b/lib/system/osalloc.nim
index de26f52d9..e864253b6 100644
--- a/lib/system/osalloc.nim
+++ b/lib/system/osalloc.nim
@@ -150,8 +150,9 @@ elif defined(windows):
     #VirtualFree(p, size, MEM_DECOMMIT)
 
 elif hostOS == "standalone":
+  const StandaloneHeapSize {.intdefine.}: int = 1024 * PageSize
   var
-    theHeap: array[1024*PageSize, float64] # 'float64' for alignment
+    theHeap: array[StandaloneHeapSize, float64] # 'float64' for alignment
     bumpPointer = cast[int](addr theHeap)
 
   proc osAllocPages(size: int): pointer {.inline.} =
diff --git a/lib/system/repr.nim b/lib/system/repr.nim
index 4da4781ef..1b80cf2f8 100644
--- a/lib/system/repr.nim
+++ b/lib/system/repr.nim
@@ -73,23 +73,20 @@ proc reprChar(x: char): string {.compilerRtl.} =
   add result, "\'"
 
 proc reprEnum(e: int, typ: PNimType): string {.compilerRtl.} =
-  # we read an 'int' but this may have been too large, so mask the other bits:
-  let b = (sizeof(int)-typ.size)*8 # bits
-  let m = 1 shl (b-1) # mask
-  var o = e and ((1 shl b)-1) # clear upper bits
-  o = (o xor m) - m # sign extend
-  # XXX we need a proper narrowing based on signedness here
-  #e and ((1 shl (typ.size*8)) - 1)
+  ## Return string representation for enumeration values
+  var n = typ.node
   if ntfEnumHole notin typ.flags:
-    if o <% typ.node.len:
-      return $typ.node.sons[o].name
+    let o = e - n.sons[0].offset
+    if o >= 0 and o <% typ.node.len:
+      return $n.sons[o].name
   else:
     # ugh we need a slow linear search:
-    var n = typ.node
     var s = n.sons
     for i in 0 .. n.len-1:
-      if s[i].offset == o: return $s[i].name
-  result = $o & " (invalid data!)"
+      if s[i].offset == e:
+        return $s[i].name
+
+  result = $e & " (invalid data!)"
 
 type
   PByteArray = ptr array[0.. 0xffff, int8]
diff --git a/lib/upcoming/asyncdispatch.nim b/lib/upcoming/asyncdispatch.nim
new file mode 100644
index 000000000..162ac5e08
--- /dev/null
+++ b/lib/upcoming/asyncdispatch.nim
@@ -0,0 +1,2154 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2015 Dominik Picheta
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+include "system/inclrtl"
+
+import os, oids, tables, strutils, macros, times, heapqueue
+
+import nativesockets, net, queues
+
+export Port, SocketFlag
+
+#{.injectStmt: newGcInvariant().}
+
+## AsyncDispatch
+## *************
+##
+## This module implements asynchronous IO. This includes a dispatcher,
+## a ``Future`` type implementation, and an ``async`` macro which allows
+## asynchronous code to be written in a synchronous style with the ``await``
+## keyword.
+##
+## The dispatcher acts as a kind of event loop. You must call ``poll`` on it
+## (or a function which does so for you such as ``waitFor`` or ``runForever``)
+## in order to poll for any outstanding events. The underlying implementation
+## is based on epoll on Linux, IO Completion Ports on Windows and select on
+## other operating systems.
+##
+## The ``poll`` function will not, on its own, return any events. Instead
+## an appropriate ``Future`` object will be completed. A ``Future`` is a
+## type which holds a value which is not yet available, but which *may* be
+## available in the future. You can check whether a future is finished
+## by using the ``finished`` function. When a future is finished it means that
+## either the value that it holds is now available or it holds an error instead.
+## The latter situation occurs when the operation to complete a future fails
+## with an exception. You can distinguish between the two situations with the
+## ``failed`` function.
+##
+## Future objects can also store a callback procedure which will be called
+## automatically once the future completes.
+##
+## Futures therefore can be thought of as an implementation of the proactor
+## pattern. In this
+## pattern you make a request for an action, and once that action is fulfilled
+## a future is completed with the result of that action. Requests can be
+## made by calling the appropriate functions. For example: calling the ``recv``
+## function will create a request for some data to be read from a socket. The
+## future which the ``recv`` function returns will then complete once the
+## requested amount of data is read **or** an exception occurs.
+##
+## Code to read some data from a socket may look something like this:
+##
+##   .. code-block::nim
+##      var future = socket.recv(100)
+##      future.callback =
+##        proc () =
+##          echo(future.read)
+##
+## All asynchronous functions returning a ``Future`` will not block. They
+## will not however return immediately. An asynchronous function will have
+## code which will be executed before an asynchronous request is made, in most
+## cases this code sets up the request.
+##
+## In the above example, the ``recv`` function will return a brand new
+## ``Future`` instance once the request for data to be read from the socket
+## is made. This ``Future`` instance will complete once the requested amount
+## of data is read, in this case it is 100 bytes. The second line sets a
+## callback on this future which will be called once the future completes.
+## All the callback does is write the data stored in the future to ``stdout``.
+## The ``read`` function is used for this and it checks whether the future
+## completes with an error for you (if it did it will simply raise the
+## error), if there is no error however it returns the value of the future.
+##
+## Asynchronous procedures
+## -----------------------
+##
+## Asynchronous procedures remove the pain of working with callbacks. They do
+## this by allowing you to write asynchronous code the same way as you would
+## write synchronous code.
+##
+## An asynchronous procedure is marked using the ``{.async.}`` pragma.
+## When marking a procedure with the ``{.async.}`` pragma it must have a
+## ``Future[T]`` return type or no return type at all. If you do not specify
+## a return type then ``Future[void]`` is assumed.
+##
+## Inside asynchronous procedures ``await`` can be used to call any
+## procedures which return a
+## ``Future``; this includes asynchronous procedures. When a procedure is
+## "awaited", the asynchronous procedure it is awaited in will
+## suspend its execution
+## until the awaited procedure's Future completes. At which point the
+## asynchronous procedure will resume its execution. During the period
+## when an asynchronous procedure is suspended other asynchronous procedures
+## will be run by the dispatcher.
+##
+## The ``await`` call may be used in many contexts. It can be used on the right
+## hand side of a variable declaration: ``var data = await socket.recv(100)``,
+## in which case the variable will be set to the value of the future
+## automatically. It can be used to await a ``Future`` object, and it can
+## be used to await a procedure returning a ``Future[void]``:
+## ``await socket.send("foobar")``.
+##
+## Discarding futures
+## ------------------
+##
+## Futures should **never** be discarded. This is because they may contain
+## errors. If you do not care for the result of a Future then you should
+## use the ``asyncCheck`` procedure instead of the ``discard`` keyword.
+##
+## Examples
+## --------
+##
+## For examples take a look at the documentation for the modules implementing
+## asynchronous IO. A good place to start is the
+## `asyncnet module <asyncnet.html>`_.
+##
+## Limitations/Bugs
+## ----------------
+##
+## * The effect system (``raises: []``) does not work with async procedures.
+## * Can't await in a ``except`` body
+## * Forward declarations for async procs are broken,
+##   link includes workaround: https://github.com/nim-lang/Nim/issues/3182.
+## * FutureVar[T] needs to be completed manually.
+
+# TODO: Check if yielded future is nil and throw a more meaningful exception
+
+# -- Futures
+
+type
+  FutureBase* = ref object of RootObj ## Untyped future.
+    cb: proc () {.closure,gcsafe.}
+    finished: bool
+    error*: ref Exception ## Stored exception
+    errorStackTrace*: string
+    when not defined(release):
+      stackTrace: string ## For debugging purposes only.
+      id: int
+      fromProc: string
+
+  Future*[T] = ref object of FutureBase ## Typed future.
+    value: T ## Stored value
+
+  FutureVar*[T] = distinct Future[T]
+
+  FutureError* = object of Exception
+    cause*: FutureBase
+
+{.deprecated: [PFutureBase: FutureBase, PFuture: Future].}
+
+when not defined(release):
+  var currentID = 0
+
+proc callSoon*(cbproc: proc ()) {.gcsafe.}
+
+proc newFuture*[T](fromProc: string = "unspecified"): Future[T] =
+  ## Creates a new future.
+  ##
+  ## Specifying ``fromProc``, which is a string specifying the name of the proc
+  ## that this future belongs to, is a good habit as it helps with debugging.
+  new(result)
+  result.finished = false
+  when not defined(release):
+    result.stackTrace = getStackTrace()
+    result.id = currentID
+    result.fromProc = fromProc
+    currentID.inc()
+
+proc newFutureVar*[T](fromProc = "unspecified"): FutureVar[T] =
+  ## Create a new ``FutureVar``. This Future type is ideally suited for
+  ## situations where you want to avoid unnecessary allocations of Futures.
+  ##
+  ## Specifying ``fromProc``, which is a string specifying the name of the proc
+  ## that this future belongs to, is a good habit as it helps with debugging.
+  result = FutureVar[T](newFuture[T](fromProc))
+
+proc clean*[T](future: FutureVar[T]) =
+  ## Resets the ``finished`` status of ``future``.
+  Future[T](future).finished = false
+  Future[T](future).error = nil
+
+proc checkFinished[T](future: Future[T]) =
+  ## Checks whether `future` is finished. If it is then raises a
+  ## ``FutureError``.
+  when not defined(release):
+    if future.finished:
+      var msg = ""
+      msg.add("An attempt was made to complete a Future more than once. ")
+      msg.add("Details:")
+      msg.add("\n  Future ID: " & $future.id)
+      msg.add("\n  Created in proc: " & future.fromProc)
+      msg.add("\n  Stack trace to moment of creation:")
+      msg.add("\n" & indent(future.stackTrace.strip(), 4))
+      when T is string:
+        msg.add("\n  Contents (string): ")
+        msg.add("\n" & indent(future.value.repr, 4))
+      msg.add("\n  Stack trace to moment of secondary completion:")
+      msg.add("\n" & indent(getStackTrace().strip(), 4))
+      var err = newException(FutureError, msg)
+      err.cause = future
+      raise err
+
+proc complete*[T](future: Future[T], val: T) =
+  ## Completes ``future`` with value ``val``.
+  #assert(not future.finished, "Future already finished, cannot finish twice.")
+  checkFinished(future)
+  assert(future.error == nil)
+  future.value = val
+  future.finished = true
+  if future.cb != nil:
+    future.cb()
+
+proc complete*(future: Future[void]) =
+  ## Completes a void ``future``.
+  #assert(not future.finished, "Future already finished, cannot finish twice.")
+  checkFinished(future)
+  assert(future.error == nil)
+  future.finished = true
+  if future.cb != nil:
+    future.cb()
+
+proc complete*[T](future: FutureVar[T]) =
+  ## Completes a ``FutureVar``.
+  template fut: expr = Future[T](future)
+  checkFinished(fut)
+  assert(fut.error == nil)
+  fut.finished = true
+  if fut.cb != nil:
+    fut.cb()
+
+proc fail*[T](future: Future[T], error: ref Exception) =
+  ## Completes ``future`` with ``error``.
+  #assert(not future.finished, "Future already finished, cannot finish twice.")
+  checkFinished(future)
+  future.finished = true
+  future.error = error
+  future.errorStackTrace =
+    if getStackTrace(error) == "": getStackTrace() else: getStackTrace(error)
+  if future.cb != nil:
+    future.cb()
+  else:
+    # This is to prevent exceptions from being silently ignored when a future
+    # is discarded.
+    # TODO: This may turn out to be a bad idea.
+    # Turns out this is a bad idea.
+    #raise error
+    discard
+
+proc `callback=`*(future: FutureBase, cb: proc () {.closure,gcsafe.}) =
+  ## Sets the callback proc to be called when the future completes.
+  ##
+  ## If future has already completed then ``cb`` will be called immediately.
+  ##
+  ## **Note**: You most likely want the other ``callback`` setter which
+  ## passes ``future`` as a param to the callback.
+  future.cb = cb
+  if future.finished:
+    callSoon(future.cb)
+
+proc `callback=`*[T](future: Future[T],
+    cb: proc (future: Future[T]) {.closure,gcsafe.}) =
+  ## Sets the callback proc to be called when the future completes.
+  ##
+  ## If future has already completed then ``cb`` will be called immediately.
+  future.callback = proc () = cb(future)
+
+proc injectStacktrace[T](future: Future[T]) =
+  # TODO: Come up with something better.
+  when not defined(release):
+    var msg = ""
+    msg.add("\n  " & future.fromProc & "'s lead up to read of failed Future:")
+
+    if not future.errorStackTrace.isNil and future.errorStackTrace != "":
+      msg.add("\n" & indent(future.errorStackTrace.strip(), 4))
+    else:
+      msg.add("\n    Empty or nil stack trace.")
+    future.error.msg.add(msg)
+
+proc read*[T](future: Future[T]): T =
+  ## Retrieves the value of ``future``. Future must be finished otherwise
+  ## this function will fail with a ``ValueError`` exception.
+  ##
+  ## If the result of the future is an error then that error will be raised.
+  if future.finished:
+    if future.error != nil:
+      injectStacktrace(future)
+      raise future.error
+    when T isnot void:
+      return future.value
+  else:
+    # TODO: Make a custom exception type for this?
+    raise newException(ValueError, "Future still in progress.")
+
+proc readError*[T](future: Future[T]): ref Exception =
+  ## Retrieves the exception stored in ``future``.
+  ##
+  ## An ``ValueError`` exception will be thrown if no exception exists
+  ## in the specified Future.
+  if future.error != nil: return future.error
+  else:
+    raise newException(ValueError, "No error in future.")
+
+proc mget*[T](future: FutureVar[T]): var T =
+  ## Returns a mutable value stored in ``future``.
+  ##
+  ## Unlike ``read``, this function will not raise an exception if the
+  ## Future has not been finished.
+  result = Future[T](future).value
+
+proc finished*[T](future: Future[T]): bool =
+  ## Determines whether ``future`` has completed.
+  ##
+  ## ``True`` may indicate an error or a value. Use ``failed`` to distinguish.
+  future.finished
+
+proc failed*(future: FutureBase): bool =
+  ## Determines whether ``future`` completed with an error.
+  return future.error != nil
+
+proc asyncCheck*[T](future: Future[T]) =
+  ## Sets a callback on ``future`` which raises an exception if the future
+  ## finished with an error.
+  ##
+  ## This should be used instead of ``discard`` to discard void futures.
+  future.callback =
+    proc () =
+      if future.failed:
+        injectStacktrace(future)
+        raise future.error
+
+proc `and`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] =
+  ## Returns a future which will complete once both ``fut1`` and ``fut2``
+  ## complete.
+  var retFuture = newFuture[void]("asyncdispatch.`and`")
+  fut1.callback =
+    proc () =
+      if fut2.finished: retFuture.complete()
+  fut2.callback =
+    proc () =
+      if fut1.finished: retFuture.complete()
+  return retFuture
+
+proc `or`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] =
+  ## Returns a future which will complete once either ``fut1`` or ``fut2``
+  ## complete.
+  var retFuture = newFuture[void]("asyncdispatch.`or`")
+  proc cb() =
+    if not retFuture.finished: retFuture.complete()
+  fut1.callback = cb
+  fut2.callback = cb
+  return retFuture
+
+proc all*[T](futs: varargs[Future[T]]): auto =
+  ## Returns a future which will complete once
+  ## all futures in ``futs`` complete.
+  ##
+  ## If the awaited futures are not ``Future[void]``, the returned future
+  ## will hold the values of all awaited futures in a sequence.
+  ##
+  ## If the awaited futures *are* ``Future[void]``,
+  ## this proc returns ``Future[void]``.
+
+  when T is void:
+    var
+      retFuture = newFuture[void]("asyncdispatch.all")
+      completedFutures = 0
+
+    let totalFutures = len(futs)
+
+    for fut in futs:
+      fut.callback = proc(f: Future[T]) =
+        inc(completedFutures)
+
+        if completedFutures == totalFutures:
+          retFuture.complete()
+
+    return retFuture
+
+  else:
+    var
+      retFuture = newFuture[seq[T]]("asyncdispatch.all")
+      retValues = newSeq[T](len(futs))
+      completedFutures = 0
+
+    for i, fut in futs:
+      proc setCallback(i: int) =
+        fut.callback = proc(f: Future[T]) =
+          retValues[i] = f.read()
+          inc(completedFutures)
+
+          if completedFutures == len(retValues):
+            retFuture.complete(retValues)
+
+      setCallback(i)
+
+    return retFuture
+
+type
+  PDispatcherBase = ref object of RootRef
+    timers: HeapQueue[tuple[finishAt: float, fut: Future[void]]]
+    callbacks: Queue[proc ()]
+
+proc processTimers(p: PDispatcherBase) {.inline.} =
+  while p.timers.len > 0 and epochTime() >= p.timers[0].finishAt:
+    p.timers.pop().fut.complete()
+
+proc processPendingCallbacks(p: PDispatcherBase) =
+  while p.callbacks.len > 0:
+    var cb = p.callbacks.dequeue()
+    cb()
+
+proc adjustedTimeout(p: PDispatcherBase, timeout: int): int {.inline.} =
+  # If dispatcher has active timers this proc returns the timeout
+  # of the nearest timer. Returns `timeout` otherwise.
+  result = timeout
+  if p.timers.len > 0:
+    let timerTimeout = p.timers[0].finishAt
+    let curTime = epochTime()
+    if timeout == -1 or (curTime + (timeout / 1000)) > timerTimeout:
+      result = int((timerTimeout - curTime) * 1000)
+      if result < 0: result = 0
+
+when defined(windows) or defined(nimdoc):
+  import winlean, sets, hashes
+  type
+    CompletionKey = ULONG_PTR
+
+    CompletionData* = object
+      fd*: AsyncFD # TODO: Rename this.
+      cb*: proc (fd: AsyncFD, bytesTransferred: Dword,
+                errcode: OSErrorCode) {.closure,gcsafe.}
+      cell*: ForeignCell # we need this `cell` to protect our `cb` environment,
+                         # when using RegisterWaitForSingleObject, because
+                         # waiting is done in different thread.
+
+    PDispatcher* = ref object of PDispatcherBase
+      ioPort: Handle
+      handles: HashSet[AsyncFD]
+
+    CustomOverlapped = object of OVERLAPPED
+      data*: CompletionData
+
+    PCustomOverlapped* = ref CustomOverlapped
+
+    AsyncFD* = distinct int
+
+    PostCallbackData = object
+      ioPort: Handle
+      handleFd: AsyncFD
+      waitFd: Handle
+      ovl: PCustomOverlapped
+    PostCallbackDataPtr = ptr PostCallbackData
+
+    AsyncEventImpl = object
+      hEvent: Handle
+      hWaiter: Handle
+      pcd: PostCallbackDataPtr
+    AsyncEvent* = ptr AsyncEventImpl
+
+    Callback = proc (fd: AsyncFD): bool {.closure,gcsafe.}
+  {.deprecated: [TCompletionKey: CompletionKey, TAsyncFD: AsyncFD,
+                TCustomOverlapped: CustomOverlapped, TCompletionData: CompletionData].}
+
+  proc hash(x: AsyncFD): Hash {.borrow.}
+  proc `==`*(x: AsyncFD, y: AsyncFD): bool {.borrow.}
+
+  proc newDispatcher*(): PDispatcher =
+    ## Creates a new Dispatcher instance.
+    new result
+    result.ioPort = createIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 1)
+    result.handles = initSet[AsyncFD]()
+    result.timers.newHeapQueue()
+    result.callbacks = initQueue[proc ()](64)
+
+  var gDisp{.threadvar.}: PDispatcher ## Global dispatcher
+  proc getGlobalDispatcher*(): PDispatcher =
+    ## Retrieves the global thread-local dispatcher.
+    if gDisp.isNil: gDisp = newDispatcher()
+    result = gDisp
+
+  proc register*(fd: AsyncFD) =
+    ## Registers ``fd`` with the dispatcher.
+    let p = getGlobalDispatcher()
+    if createIoCompletionPort(fd.Handle, p.ioPort,
+                              cast[CompletionKey](fd), 1) == 0:
+      raiseOSError(osLastError())
+    p.handles.incl(fd)
+
+  proc verifyPresence(fd: AsyncFD) =
+    ## Ensures that file descriptor has been registered with the dispatcher.
+    let p = getGlobalDispatcher()
+    if fd notin p.handles:
+      raise newException(ValueError,
+        "Operation performed on a socket which has not been registered with" &
+        " the dispatcher yet.")
+
+  proc poll*(timeout = 500) =
+    ## Waits for completion events and processes them.
+    let p = getGlobalDispatcher()
+    if p.handles.len == 0 and p.timers.len == 0 and p.callbacks.len == 0:
+      raise newException(ValueError,
+        "No handles or timers registered in dispatcher.")
+
+    let at = p.adjustedTimeout(timeout)
+    var llTimeout =
+      if at == -1: winlean.INFINITE
+      else: at.int32
+
+    var lpNumberOfBytesTransferred: Dword
+    var lpCompletionKey: ULONG_PTR
+    var customOverlapped: PCustomOverlapped
+    let res = getQueuedCompletionStatus(p.ioPort,
+        addr lpNumberOfBytesTransferred, addr lpCompletionKey,
+        cast[ptr POVERLAPPED](addr customOverlapped), llTimeout).bool
+
+    # http://stackoverflow.com/a/12277264/492186
+    # TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html
+    if res:
+      # This is useful for ensuring the reliability of the overlapped struct.
+      assert customOverlapped.data.fd == lpCompletionKey.AsyncFD
+
+      customOverlapped.data.cb(customOverlapped.data.fd,
+          lpNumberOfBytesTransferred, OSErrorCode(-1))
+
+      # If cell.data != nil, then system.protect(rawEnv(cb)) was called,
+      # so we need to dispose our `cb` environment, because it is not needed
+      # anymore.
+      if customOverlapped.data.cell.data != nil:
+        system.dispose(customOverlapped.data.cell)
+
+      GC_unref(customOverlapped)
+    else:
+      let errCode = osLastError()
+      if customOverlapped != nil:
+        assert customOverlapped.data.fd == lpCompletionKey.AsyncFD
+        customOverlapped.data.cb(customOverlapped.data.fd,
+            lpNumberOfBytesTransferred, errCode)
+        if customOverlapped.data.cell.data != nil:
+          system.dispose(customOverlapped.data.cell)
+        GC_unref(customOverlapped)
+      else:
+        if errCode.int32 == WAIT_TIMEOUT:
+          # Timed out
+          discard
+        else: raiseOSError(errCode)
+
+    # Timer processing.
+    processTimers(p)
+    # Callback queue processing
+    processPendingCallbacks(p)
+
+  var acceptEx*: WSAPROC_ACCEPTEX
+  var connectEx*: WSAPROC_CONNECTEX
+  var getAcceptExSockAddrs*: WSAPROC_GETACCEPTEXSOCKADDRS
+
+  proc initPointer(s: SocketHandle, fun: var pointer, guid: var GUID): bool =
+    # Ref: https://github.com/powdahound/twisted/blob/master/twisted/internet/iocpreactor/iocpsupport/winsock_pointers.c
+    var bytesRet: Dword
+    fun = nil
+    result = WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, addr guid,
+                      sizeof(GUID).Dword, addr fun, sizeof(pointer).Dword,
+                      addr bytesRet, nil, nil) == 0
+
+  proc initAll() =
+    let dummySock = newNativeSocket()
+    if dummySock == INVALID_SOCKET:
+      raiseOSError(osLastError())
+    var fun: pointer = nil
+    if not initPointer(dummySock, fun, WSAID_CONNECTEX):
+      raiseOSError(osLastError())
+    connectEx = cast[WSAPROC_CONNECTEX](fun)
+    if not initPointer(dummySock, fun, WSAID_ACCEPTEX):
+      raiseOSError(osLastError())
+    acceptEx = cast[WSAPROC_ACCEPTEX](fun)
+    if not initPointer(dummySock, fun, WSAID_GETACCEPTEXSOCKADDRS):
+      raiseOSError(osLastError())
+    getAcceptExSockAddrs = cast[WSAPROC_GETACCEPTEXSOCKADDRS](fun)
+    close(dummySock)
+
+  proc connect*(socket: AsyncFD, address: string, port: Port,
+    domain = nativesockets.AF_INET): Future[void] =
+    ## Connects ``socket`` to server at ``address:port``.
+    ##
+    ## Returns a ``Future`` which will complete when the connection succeeds
+    ## or an error occurs.
+    verifyPresence(socket)
+    var retFuture = newFuture[void]("connect")
+    # Apparently ``ConnectEx`` expects the socket to be initially bound:
+    var saddr: Sockaddr_in
+    saddr.sin_family = int16(toInt(domain))
+    saddr.sin_port = 0
+    saddr.sin_addr.s_addr = INADDR_ANY
+    if bindAddr(socket.SocketHandle, cast[ptr SockAddr](addr(saddr)),
+                  sizeof(saddr).SockLen) < 0'i32:
+      raiseOSError(osLastError())
+
+    var aiList = getAddrInfo(address, port, domain)
+    var success = false
+    var lastError: OSErrorCode
+    var it = aiList
+    while it != nil:
+      # "the OVERLAPPED structure must remain valid until the I/O completes"
+      # http://blogs.msdn.com/b/oldnewthing/archive/2011/02/02/10123392.aspx
+      var ol = PCustomOverlapped()
+      GC_ref(ol)
+      ol.data = CompletionData(fd: socket, cb:
+        proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
+          if not retFuture.finished:
+            if errcode == OSErrorCode(-1):
+              retFuture.complete()
+            else:
+              retFuture.fail(newException(OSError, osErrorMsg(errcode)))
+      )
+
+      var ret = connectEx(socket.SocketHandle, it.ai_addr,
+                          sizeof(Sockaddr_in).cint, nil, 0, nil,
+                          cast[POVERLAPPED](ol))
+      if ret:
+        # Request to connect completed immediately.
+        success = true
+        retFuture.complete()
+        # We don't deallocate ``ol`` here because even though this completed
+        # immediately poll will still be notified about its completion and it will
+        # free ``ol``.
+        break
+      else:
+        lastError = osLastError()
+        if lastError.int32 == ERROR_IO_PENDING:
+          # In this case ``ol`` will be deallocated in ``poll``.
+          success = true
+          break
+        else:
+          GC_unref(ol)
+          success = false
+      it = it.ai_next
+
+    dealloc(aiList)
+    if not success:
+      retFuture.fail(newException(OSError, osErrorMsg(lastError)))
+    return retFuture
+
+  proc recv*(socket: AsyncFD, size: int,
+             flags = {SocketFlag.SafeDisconn}): Future[string] =
+    ## Reads **up to** ``size`` bytes from ``socket``. Returned future will
+    ## complete once all the data requested is read, a part of the data has been
+    ## read, or the socket has disconnected in which case the future will
+    ## complete with a value of ``""``.
+    ##
+    ## **Warning**: The ``Peek`` socket flag is not supported on Windows.
+
+
+    # Things to note:
+    #   * When WSARecv completes immediately then ``bytesReceived`` is very
+    #     unreliable.
+    #   * Still need to implement message-oriented socket disconnection,
+    #     '\0' in the message currently signifies a socket disconnect. Who
+    #     knows what will happen when someone sends that to our socket.
+    verifyPresence(socket)
+    assert SocketFlag.Peek notin flags, "Peek not supported on Windows."
+
+    var retFuture = newFuture[string]("recv")
+    var dataBuf: TWSABuf
+    dataBuf.buf = cast[cstring](alloc0(size))
+    dataBuf.len = size.ULONG
+
+    var bytesReceived: Dword
+    var flagsio = flags.toOSFlags().Dword
+    var ol = PCustomOverlapped()
+    GC_ref(ol)
+    ol.data = CompletionData(fd: socket, cb:
+      proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
+        if not retFuture.finished:
+          if errcode == OSErrorCode(-1):
+            if bytesCount == 0 and dataBuf.buf[0] == '\0':
+              retFuture.complete("")
+            else:
+              var data = newString(bytesCount)
+              assert bytesCount <= size
+              copyMem(addr data[0], addr dataBuf.buf[0], bytesCount)
+              retFuture.complete($data)
+          else:
+            if flags.isDisconnectionError(errcode):
+              retFuture.complete("")
+            else:
+              retFuture.fail(newException(OSError, osErrorMsg(errcode)))
+        if dataBuf.buf != nil:
+          dealloc dataBuf.buf
+          dataBuf.buf = nil
+    )
+
+    let ret = WSARecv(socket.SocketHandle, addr dataBuf, 1, addr bytesReceived,
+                      addr flagsio, cast[POVERLAPPED](ol), nil)
+    if ret == -1:
+      let err = osLastError()
+      if err.int32 != ERROR_IO_PENDING:
+        if dataBuf.buf != nil:
+          dealloc dataBuf.buf
+          dataBuf.buf = nil
+        GC_unref(ol)
+        if flags.isDisconnectionError(err):
+          retFuture.complete("")
+        else:
+          retFuture.fail(newException(OSError, osErrorMsg(err)))
+    elif ret == 0:
+      # Request completed immediately.
+      if bytesReceived != 0:
+        var data = newString(bytesReceived)
+        assert bytesReceived <= size
+        copyMem(addr data[0], addr dataBuf.buf[0], bytesReceived)
+        retFuture.complete($data)
+      else:
+        if hasOverlappedIoCompleted(cast[POVERLAPPED](ol)):
+          retFuture.complete("")
+    return retFuture
+
+  proc recvInto*(socket: AsyncFD, buf: cstring, size: int,
+                flags = {SocketFlag.SafeDisconn}): Future[int] =
+    ## Reads **up to** ``size`` bytes from ``socket`` into ``buf``, which must
+    ## at least be of that size. Returned future will complete once all the
+    ## data requested is read, a part of the data has been read, or the socket
+    ## has disconnected in which case the future will complete with a value of
+    ## ``0``.
+    ##
+    ## **Warning**: The ``Peek`` socket flag is not supported on Windows.
+
+
+    # Things to note:
+    #   * When WSARecv completes immediately then ``bytesReceived`` is very
+    #     unreliable.
+    #   * Still need to implement message-oriented socket disconnection,
+    #     '\0' in the message currently signifies a socket disconnect. Who
+    #     knows what will happen when someone sends that to our socket.
+    verifyPresence(socket)
+    assert SocketFlag.Peek notin flags, "Peek not supported on Windows."
+
+    var retFuture = newFuture[int]("recvInto")
+
+    #buf[] = '\0'
+    var dataBuf: TWSABuf
+    dataBuf.buf = buf
+    dataBuf.len = size.ULONG
+
+    var bytesReceived: Dword
+    var flagsio = flags.toOSFlags().Dword
+    var ol = PCustomOverlapped()
+    GC_ref(ol)
+    ol.data = CompletionData(fd: socket, cb:
+      proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
+        if not retFuture.finished:
+          if errcode == OSErrorCode(-1):
+            if bytesCount == 0 and dataBuf.buf[0] == '\0':
+              retFuture.complete(0)
+            else:
+              retFuture.complete(bytesCount)
+          else:
+            if flags.isDisconnectionError(errcode):
+              retFuture.complete(0)
+            else:
+              retFuture.fail(newException(OSError, osErrorMsg(errcode)))
+        if dataBuf.buf != nil:
+          dataBuf.buf = nil
+    )
+
+    let ret = WSARecv(socket.SocketHandle, addr dataBuf, 1, addr bytesReceived,
+                      addr flagsio, cast[POVERLAPPED](ol), nil)
+    if ret == -1:
+      let err = osLastError()
+      if err.int32 != ERROR_IO_PENDING:
+        if dataBuf.buf != nil:
+          dataBuf.buf = nil
+        GC_unref(ol)
+        if flags.isDisconnectionError(err):
+          retFuture.complete(0)
+        else:
+          retFuture.fail(newException(OSError, osErrorMsg(err)))
+    elif ret == 0:
+      # Request completed immediately.
+      if bytesReceived != 0:
+        assert bytesReceived <= size
+        retFuture.complete(bytesReceived)
+      else:
+        if hasOverlappedIoCompleted(cast[POVERLAPPED](ol)):
+          retFuture.complete(bytesReceived)
+    return retFuture
+
+  proc send*(socket: AsyncFD, data: string,
+             flags = {SocketFlag.SafeDisconn}): Future[void] =
+    ## Sends ``data`` to ``socket``. The returned future will complete once all
+    ## data has been sent.
+    verifyPresence(socket)
+    var retFuture = newFuture[void]("send")
+
+    var dataBuf: TWSABuf
+    dataBuf.buf = data # since this is not used in a callback, this is fine
+    dataBuf.len = data.len.ULONG
+
+    var bytesReceived, lowFlags: Dword
+    var ol = PCustomOverlapped()
+    GC_ref(ol)
+    ol.data = CompletionData(fd: socket, cb:
+      proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
+        if not retFuture.finished:
+          if errcode == OSErrorCode(-1):
+            retFuture.complete()
+          else:
+            if flags.isDisconnectionError(errcode):
+              retFuture.complete()
+            else:
+              retFuture.fail(newException(OSError, osErrorMsg(errcode)))
+    )
+
+    let ret = WSASend(socket.SocketHandle, addr dataBuf, 1, addr bytesReceived,
+                      lowFlags, cast[POVERLAPPED](ol), nil)
+    if ret == -1:
+      let err = osLastError()
+      if err.int32 != ERROR_IO_PENDING:
+        GC_unref(ol)
+        if flags.isDisconnectionError(err):
+          retFuture.complete()
+        else:
+          retFuture.fail(newException(OSError, osErrorMsg(err)))
+    else:
+      retFuture.complete()
+      # We don't deallocate ``ol`` here because even though this completed
+      # immediately poll will still be notified about its completion and it will
+      # free ``ol``.
+    return retFuture
+
+  proc sendTo*(socket: AsyncFD, data: pointer, size: int, saddr: ptr SockAddr,
+               saddrLen: Socklen,
+               flags = {SocketFlag.SafeDisconn}): Future[void] =
+    ## Sends ``data`` to specified destination ``saddr``, using
+    ## socket ``socket``. The returned future will complete once all data
+    ## has been sent.
+    verifyPresence(socket)
+    var retFuture = newFuture[void]("sendTo")
+    var dataBuf: TWSABuf
+    dataBuf.buf = cast[cstring](data)
+    dataBuf.len = size.ULONG
+    var bytesSent = 0.Dword
+    var lowFlags = 0.Dword
+
+    # we will preserve address in our stack
+    var staddr: array[128, char] # SOCKADDR_STORAGE size is 128 bytes
+    var stalen: cint = cint(saddrLen)
+    zeroMem(addr(staddr[0]), 128)
+    copyMem(addr(staddr[0]), saddr, saddrLen)
+
+    var ol = PCustomOverlapped()
+    GC_ref(ol)
+    ol.data = CompletionData(fd: socket, cb:
+      proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
+        if not retFuture.finished:
+          if errcode == OSErrorCode(-1):
+            retFuture.complete()
+          else:
+            retFuture.fail(newException(OSError, osErrorMsg(errcode)))
+    )
+
+    let ret = WSASendTo(socket.SocketHandle, addr dataBuf, 1, addr bytesSent,
+                        lowFlags, cast[ptr SockAddr](addr(staddr[0])),
+                        stalen, cast[POVERLAPPED](ol), nil)
+    if ret == -1:
+      let err = osLastError()
+      if err.int32 != ERROR_IO_PENDING:
+        GC_unref(ol)
+        retFuture.fail(newException(OSError, osErrorMsg(err)))
+    else:
+      retFuture.complete()
+      # We don't deallocate ``ol`` here because even though this completed
+      # immediately poll will still be notified about its completion and it will
+      # free ``ol``.
+    return retFuture
+
+  proc recvFromInto*(socket: AsyncFD, data: pointer, size: int,
+                     saddr: ptr SockAddr, saddrLen: ptr SockLen,
+                     flags = {SocketFlag.SafeDisconn}): Future[int] =
+    ## Receives a datagram data from ``socket`` into ``buf``, which must
+    ## be at least of size ``size``, address of datagram's sender will be
+    ## stored into ``saddr`` and ``saddrLen``. Returned future will complete
+    ## once one datagram has been received, and will return size of packet
+    ## received.
+    verifyPresence(socket)
+    var retFuture = newFuture[int]("recvFromInto")
+
+    var dataBuf = TWSABuf(buf: cast[cstring](data), len: size.ULONG)
+
+    var bytesReceived = 0.Dword
+    var lowFlags = 0.Dword
+
+    var ol = PCustomOverlapped()
+    GC_ref(ol)
+    ol.data = CompletionData(fd: socket, cb:
+      proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
+        if not retFuture.finished:
+          if errcode == OSErrorCode(-1):
+            assert bytesCount <= size
+            retFuture.complete(bytesCount)
+          else:
+            # datagram sockets don't have disconnection,
+            # so we can just raise an exception
+            retFuture.fail(newException(OSError, osErrorMsg(errcode)))
+    )
+
+    let res = WSARecvFrom(socket.SocketHandle, addr dataBuf, 1,
+                          addr bytesReceived, addr lowFlags,
+                          saddr, cast[ptr cint](saddrLen),
+                          cast[POVERLAPPED](ol), nil)
+    if res == -1:
+      let err = osLastError()
+      if err.int32 != ERROR_IO_PENDING:
+        GC_unref(ol)
+        retFuture.fail(newException(OSError, osErrorMsg(err)))
+    else:
+      # Request completed immediately.
+      if bytesReceived != 0:
+        assert bytesReceived <= size
+        retFuture.complete(bytesReceived)
+      else:
+        if hasOverlappedIoCompleted(cast[POVERLAPPED](ol)):
+          retFuture.complete(bytesReceived)
+    return retFuture
+
+  proc acceptAddr*(socket: AsyncFD, flags = {SocketFlag.SafeDisconn}):
+      Future[tuple[address: string, client: AsyncFD]] =
+    ## Accepts a new connection. Returns a future containing the client socket
+    ## corresponding to that connection and the remote address of the client.
+    ## The future will complete when the connection is successfully accepted.
+    ##
+    ## The resulting client socket is automatically registered to the
+    ## dispatcher.
+    ##
+    ## The ``accept`` call may result in an error if the connecting socket
+    ## disconnects during the duration of the ``accept``. If the ``SafeDisconn``
+    ## flag is specified then this error will not be raised and instead
+    ## accept will be called again.
+    verifyPresence(socket)
+    var retFuture = newFuture[tuple[address: string, client: AsyncFD]]("acceptAddr")
+
+    var clientSock = newNativeSocket()
+    if clientSock == osInvalidSocket: raiseOSError(osLastError())
+
+    const lpOutputLen = 1024
+    var lpOutputBuf = newString(lpOutputLen)
+    var dwBytesReceived: Dword
+    let dwReceiveDataLength = 0.Dword # We don't want any data to be read.
+    let dwLocalAddressLength = Dword(sizeof (Sockaddr_in) + 16)
+    let dwRemoteAddressLength = Dword(sizeof(Sockaddr_in) + 16)
+
+    template completeAccept(): stmt {.immediate, dirty.} =
+      var listenSock = socket
+      let setoptRet = setsockopt(clientSock, SOL_SOCKET,
+          SO_UPDATE_ACCEPT_CONTEXT, addr listenSock,
+          sizeof(listenSock).SockLen)
+      if setoptRet != 0: raiseOSError(osLastError())
+
+      var localSockaddr, remoteSockaddr: ptr SockAddr
+      var localLen, remoteLen: int32
+      getAcceptExSockaddrs(addr lpOutputBuf[0], dwReceiveDataLength,
+                           dwLocalAddressLength, dwRemoteAddressLength,
+                           addr localSockaddr, addr localLen,
+                           addr remoteSockaddr, addr remoteLen)
+      register(clientSock.AsyncFD)
+      # TODO: IPv6. Check ``sa_family``. http://stackoverflow.com/a/9212542/492186
+      retFuture.complete(
+        (address: $inet_ntoa(cast[ptr Sockaddr_in](remoteSockAddr).sin_addr),
+         client: clientSock.AsyncFD)
+      )
+
+    template failAccept(errcode): stmt =
+      if flags.isDisconnectionError(errcode):
+        var newAcceptFut = acceptAddr(socket, flags)
+        newAcceptFut.callback =
+          proc () =
+            if newAcceptFut.failed:
+              retFuture.fail(newAcceptFut.readError)
+            else:
+              retFuture.complete(newAcceptFut.read)
+      else:
+        retFuture.fail(newException(OSError, osErrorMsg(errcode)))
+
+    var ol = PCustomOverlapped()
+    GC_ref(ol)
+    ol.data = CompletionData(fd: socket, cb:
+      proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
+        if not retFuture.finished:
+          if errcode == OSErrorCode(-1):
+            completeAccept()
+          else:
+            failAccept(errcode)
+    )
+
+    # http://msdn.microsoft.com/en-us/library/windows/desktop/ms737524%28v=vs.85%29.aspx
+    let ret = acceptEx(socket.SocketHandle, clientSock, addr lpOutputBuf[0],
+                       dwReceiveDataLength,
+                       dwLocalAddressLength,
+                       dwRemoteAddressLength,
+                       addr dwBytesReceived, cast[POVERLAPPED](ol))
+
+    if not ret:
+      let err = osLastError()
+      if err.int32 != ERROR_IO_PENDING:
+        failAccept(err)
+        GC_unref(ol)
+    else:
+      completeAccept()
+      # We don't deallocate ``ol`` here because even though this completed
+      # immediately poll will still be notified about its completion and it will
+      # free ``ol``.
+
+    return retFuture
+
+  proc newAsyncNativeSocket*(domain, sockType, protocol: cint): AsyncFD =
+    ## Creates a new socket and registers it with the dispatcher implicitly.
+    result = newNativeSocket(domain, sockType, protocol).AsyncFD
+    result.SocketHandle.setBlocking(false)
+    register(result)
+
+  proc newAsyncNativeSocket*(domain: Domain = nativesockets.AF_INET,
+                             sockType: SockType = SOCK_STREAM,
+                             protocol: Protocol = IPPROTO_TCP): AsyncFD =
+    ## Creates a new socket and registers it with the dispatcher implicitly.
+    result = newNativeSocket(domain, sockType, protocol).AsyncFD
+    result.SocketHandle.setBlocking(false)
+    register(result)
+
+  proc closeSocket*(socket: AsyncFD) =
+    ## Closes a socket and ensures that it is unregistered.
+    socket.SocketHandle.close()
+    getGlobalDispatcher().handles.excl(socket)
+
+  proc unregister*(fd: AsyncFD) =
+    ## Unregisters ``fd``.
+    getGlobalDispatcher().handles.excl(fd)
+
+  {.push stackTrace:off.}
+  proc waitableCallback(param: pointer,
+                        timerOrWaitFired: WINBOOL): void {.stdcall.} =
+    var p = cast[PostCallbackDataPtr](param)
+    discard postQueuedCompletionStatus(p.ioPort, timerOrWaitFired.Dword,
+                                       ULONG_PTR(p.handleFd),
+                                       cast[pointer](p.ovl))
+  {.pop.}
+
+  template registerWaitableEvent(mask) =
+    let p = getGlobalDispatcher()
+    var flags = (WT_EXECUTEINWAITTHREAD or WT_EXECUTEONLYONCE).Dword
+    var hEvent = wsaCreateEvent()
+    if hEvent == 0:
+      raiseOSError(osLastError())
+    var pcd = cast[PostCallbackDataPtr](allocShared0(sizeof(PostCallbackData)))
+    pcd.ioPort = p.ioPort
+    pcd.handleFd = fd
+    var ol = PCustomOverlapped()
+    GC_ref(ol)
+
+    ol.data = CompletionData(fd: fd, cb:
+      proc(fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
+        # we excluding our `fd` because cb(fd) can register own handler
+        # for this `fd`
+        p.handles.excl(fd)
+        # unregisterWait() is called before callback, because appropriate
+        # winsockets function can re-enable event.
+        # https://msdn.microsoft.com/en-us/library/windows/desktop/ms741576(v=vs.85).aspx
+        if unregisterWait(pcd.waitFd) == 0:
+          let err = osLastError()
+          if err.int32 != ERROR_IO_PENDING:
+            raiseOSError(osLastError())
+        if cb(fd):
+          # callback returned `true`, so we free all allocated resources
+          deallocShared(cast[pointer](pcd))
+          if not wsaCloseEvent(hEvent):
+            raiseOSError(osLastError())
+          # pcd.ovl will be unrefed in poll().
+        else:
+          # callback returned `false` we need to continue
+          if p.handles.contains(fd):
+            # new callback was already registered with `fd`, so we free all
+            # allocated resources. This happens because in callback `cb`
+            # addRead/addWrite was called with same `fd`.
+            deallocShared(cast[pointer](pcd))
+            if not wsaCloseEvent(hEvent):
+              raiseOSError(osLastError())
+          else:
+            # we need to include `fd` again
+            p.handles.incl(fd)
+            # and register WaitForSingleObject again
+            if not registerWaitForSingleObject(addr(pcd.waitFd), hEvent,
+                                    cast[WAITORTIMERCALLBACK](waitableCallback),
+                                       cast[pointer](pcd), INFINITE, flags):
+              # pcd.ovl will be unrefed in poll()
+              discard wsaCloseEvent(hEvent)
+              deallocShared(cast[pointer](pcd))
+              raiseOSError(osLastError())
+            else:
+              # we ref pcd.ovl one more time, because it will be unrefed in
+              # poll()
+              GC_ref(pcd.ovl)
+    )
+    # We need to protect our callback environment value, so GC will not free it
+    # accidentally.
+    ol.data.cell = system.protect(rawEnv(ol.data.cb))
+
+    # This is main part of `hacky way` is using WSAEventSelect, so `hEvent`
+    # will be signaled when appropriate `mask` events will be triggered.
+    if wsaEventSelect(fd.SocketHandle, hEvent, mask) != 0:
+      GC_unref(ol)
+      deallocShared(cast[pointer](pcd))
+      discard wsaCloseEvent(hEvent)
+      raiseOSError(osLastError())
+
+    pcd.ovl = ol
+    if not registerWaitForSingleObject(addr(pcd.waitFd), hEvent,
+                                    cast[WAITORTIMERCALLBACK](waitableCallback),
+                                       cast[pointer](pcd), INFINITE, flags):
+      GC_unref(ol)
+      deallocShared(cast[pointer](pcd))
+      discard wsaCloseEvent(hEvent)
+      raiseOSError(osLastError())
+    p.handles.incl(fd)
+
+  proc addRead*(fd: AsyncFD, cb: Callback) =
+    ## Start watching the file descriptor for read availability and then call
+    ## the callback ``cb``.
+    ##
+    ## This is not ``pure`` mechanism for Windows Completion Ports (IOCP),
+    ## so if you can avoid it, please do it. Use `addRead` only if really
+    ## need it (main usecase is adaptation of `unix like` libraries to be
+    ## asynchronous on Windows).
+    ## If you use this function, you dont need to use asyncdispatch.recv()
+    ## or asyncdispatch.accept(), because they are using IOCP, please use
+    ## nativesockets.recv() and nativesockets.accept() instead.
+    ##
+    ## Be sure your callback ``cb`` returns ``true``, if you want to remove
+    ## watch of `read` notifications, and ``false``, if you want to continue
+    ## receiving notifies.
+    registerWaitableEvent(FD_READ or FD_ACCEPT or FD_OOB or FD_CLOSE)
+
+  proc addWrite*(fd: AsyncFD, cb: Callback) =
+    ## Start watching the file descriptor for write availability and then call
+    ## the callback ``cb``.
+    ##
+    ## This is not ``pure`` mechanism for Windows Completion Ports (IOCP),
+    ## so if you can avoid it, please do it. Use `addWrite` only if really
+    ## need it (main usecase is adaptation of `unix like` libraries to be
+    ## asynchronous on Windows).
+    ## If you use this function, you dont need to use asyncdispatch.send()
+    ## or asyncdispatch.connect(), because they are using IOCP, please use
+    ## nativesockets.send() and nativesockets.connect() instead.
+    ##
+    ## Be sure your callback ``cb`` returns ``true``, if you want to remove
+    ## watch of `write` notifications, and ``false``, if you want to continue
+    ## receiving notifies.
+    registerWaitableEvent(FD_WRITE or FD_CONNECT or FD_CLOSE)
+
+  template registerWaitableHandle(p, hEvent, flags, pcd, handleCallback) =
+    let handleFD = AsyncFD(hEvent)
+    pcd.ioPort = p.ioPort
+    pcd.handleFd = handleFD
+    var ol = PCustomOverlapped()
+    GC_ref(ol)
+    ol.data = CompletionData(fd: handleFD, cb: handleCallback)
+    # We need to protect our callback environment value, so GC will not free it
+    # accidentally.
+    ol.data.cell = system.protect(rawEnv(ol.data.cb))
+
+    pcd.ovl = ol
+    if not registerWaitForSingleObject(addr(pcd.waitFd), hEvent,
+                                    cast[WAITORTIMERCALLBACK](waitableCallback),
+                                       cast[pointer](pcd), INFINITE, flags):
+      GC_unref(ol)
+      deallocShared(cast[pointer](pcd))
+      discard wsaCloseEvent(hEvent)
+      raiseOSError(osLastError())
+    p.handles.incl(handleFD)
+
+  proc addTimer*(timeout: int, oneshot: bool, cb: Callback) =
+    ## Registers callback ``cb`` to be called when timer expired.
+    ## ``timeout`` - timeout value in milliseconds.
+    ## ``oneshot`` - `true`, to generate only one timeout event, `false`, to
+    ## generate timeout events periodically.
+
+    doAssert(timeout > 0)
+    let p = getGlobalDispatcher()
+
+    var hEvent = createEvent(nil, 1, 0, nil)
+    if hEvent == INVALID_HANDLE_VALUE:
+      raiseOSError(osLastError())
+
+    var pcd = cast[PostCallbackDataPtr](allocShared0(sizeof(PostCallbackData)))
+    var flags = WT_EXECUTEINWAITTHREAD.Dword
+    if oneshot: flags = flags or WT_EXECUTEONLYONCE
+
+    proc timercb(fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
+      let res = cb(fd)
+      if res or oneshot:
+        if unregisterWait(pcd.waitFd) == 0:
+          let err = osLastError()
+          if err.int32 != ERROR_IO_PENDING:
+            raiseOSError(osLastError())
+        discard closeHandle(hEvent)
+        deallocShared(cast[pointer](pcd))
+        p.handles.excl(fd)
+
+    registerWaitableHandle(p, hEvent, flags, pcd, timercb)
+
+  proc addProcess*(pid: int, cb: Callback) =
+    ## Registers callback ``cb`` to be called when process with pid ``pid``
+    ## exited.
+    let p = getGlobalDispatcher()
+    let procFlags = SYNCHRONIZE
+    var hProcess = openProcess(procFlags, 0, pid.Dword)
+    if hProcess == INVALID_HANDLE_VALUE:
+      raiseOSError(osLastError())
+
+    var pcd = cast[PostCallbackDataPtr](allocShared0(sizeof(PostCallbackData)))
+    var flags = WT_EXECUTEINWAITTHREAD.Dword
+
+    proc proccb(fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
+      if unregisterWait(pcd.waitFd) == 0:
+        let err = osLastError()
+        if err.int32 != ERROR_IO_PENDING:
+          raiseOSError(osLastError())
+      discard closeHandle(hProcess)
+      deallocShared(cast[pointer](pcd))
+      p.handles.excl(fd)
+      discard cb(fd)
+
+    registerWaitableHandle(p, hProcess, flags, pcd, proccb)
+
+  proc newEvent*(): AsyncEvent =
+    ## Creates new ``AsyncEvent`` object.
+    var sa = SECURITY_ATTRIBUTES(
+      nLength: sizeof(SECURITY_ATTRIBUTES).cint,
+      bInheritHandle: 1
+    )
+    var event = createEvent(addr(sa), 0'i32, 0'i32, nil)
+    if event == INVALID_HANDLE_VALUE:
+      raiseOSError(osLastError())
+    result = cast[AsyncEvent](allocShared0(sizeof(AsyncEventImpl)))
+
+  proc setEvent*(ev: AsyncEvent) =
+    ## Set event ``ev`` to signaled state.
+    if setEvent(ev.hEvent) == 0:
+      raiseOSError(osLastError())
+
+  proc close*(ev: AsyncEvent) =
+    ## Closes event ``ev``.
+    if ev.hWaiter != 0:
+      let p = getGlobalDispatcher()
+      if unregisterWait(ev.hWaiter) == 0:
+        let err = osLastError()
+        if err.int32 != ERROR_IO_PENDING:
+          raiseOSError(osLastError())
+      p.handles.excl(AsyncFD(ev.hEvent))
+
+    if closeHandle(ev.hEvent) == 0:
+      raiseOSError(osLastError())
+    deallocShared(cast[pointer](ev))
+
+  proc addEvent*(ev: AsyncEvent, cb: Callback) =
+    ## Registers callback ``cb`` to be called when ``ev`` will be signaled
+    if ev.hWaiter != 0:
+      raise newException(ValueError, "Event is already registered!")
+
+    let p = getGlobalDispatcher()
+    let hEvent = ev.hEvent
+
+    var pcd = cast[PostCallbackDataPtr](allocShared0(sizeof(PostCallbackData)))
+    var flags = WT_EXECUTEINWAITTHREAD.Dword
+
+    proc eventcb(fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
+      if cb(fd):
+        if unregisterWait(pcd.waitFd) == 0:
+          let err = osLastError()
+          if err.int32 != ERROR_IO_PENDING:
+            raiseOSError(osLastError())
+        ev.hWaiter = 0
+        deallocShared(cast[pointer](pcd))
+        p.handles.excl(fd)
+
+    registerWaitableHandle(p, hEvent, flags, pcd, eventcb)
+    ev.hWaiter = pcd.waitFd
+
+  initAll()
+else:
+  import ioselectors
+  when defined(windows):
+    import winlean
+    const
+      EINTR = WSAEINPROGRESS
+      EINPROGRESS = WSAEINPROGRESS
+      EWOULDBLOCK = WSAEWOULDBLOCK
+      EAGAIN = EINPROGRESS
+      MSG_NOSIGNAL = 0
+  else:
+    from posix import EINTR, EAGAIN, EINPROGRESS, EWOULDBLOCK, MSG_PEEK,
+                      MSG_NOSIGNAL
+
+  const supportedPlatform = defined(linux) or defined(freebsd) or
+                            defined(netbsd) or defined(openbsd) or
+                            defined(macosx)
+
+  type
+    AsyncFD* = distinct cint
+    Callback = proc (fd: AsyncFD): bool {.closure,gcsafe.}
+
+    AsyncData = object
+      readCB: Callback
+      writeCB: Callback
+
+    AsyncEvent = SelectEvent
+
+    PDispatcher* = ref object of PDispatcherBase
+      selector: Selector[AsyncData]
+  {.deprecated: [TAsyncFD: AsyncFD, TCallback: Callback].}
+
+  proc `==`*(x, y: AsyncFD): bool {.borrow.}
+
+  proc newDispatcher*(): PDispatcher =
+    new result
+    result.selector = newSelector[AsyncData]()
+    result.timers.newHeapQueue()
+    result.callbacks = initQueue[proc ()](64)
+
+  var gDisp{.threadvar.}: PDispatcher ## Global dispatcher
+  proc getGlobalDispatcher*(): PDispatcher =
+    if gDisp.isNil: gDisp = newDispatcher()
+    result = gDisp
+
+  proc register*(fd: AsyncFD) =
+    let p = getGlobalDispatcher()
+    var data = AsyncData()
+    p.selector.registerHandle(fd.SocketHandle, {}, data)
+
+  proc newAsyncNativeSocket*(domain: cint, sockType: cint,
+                             protocol: cint): AsyncFD =
+    result = newNativeSocket(domain, sockType, protocol).AsyncFD
+    result.SocketHandle.setBlocking(false)
+    when defined(macosx):
+      result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
+    register(result)
+
+  proc newAsyncNativeSocket*(domain: Domain = AF_INET,
+                             sockType: SockType = SOCK_STREAM,
+                             protocol: Protocol = IPPROTO_TCP): AsyncFD =
+    result = newNativeSocket(domain, sockType, protocol).AsyncFD
+    result.SocketHandle.setBlocking(false)
+    when defined(macosx):
+      result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
+    register(result)
+
+  proc closeSocket*(sock: AsyncFD) =
+    let disp = getGlobalDispatcher()
+    disp.selector.unregister(sock.SocketHandle)
+    sock.SocketHandle.close()
+
+  proc unregister*(fd: AsyncFD) =
+    getGlobalDispatcher().selector.unregister(fd.SocketHandle)
+
+  # proc unregister*(ev: AsyncEvent) =
+  #   getGlobalDispatcher().selector.unregister(SelectEvent(ev))
+
+  proc addRead*(fd: AsyncFD, cb: Callback) =
+    let p = getGlobalDispatcher()
+    withData(p.selector, fd.SocketHandle, adata) do:
+      adata.readCB = cb
+    do:
+      raise newException(ValueError, "File descriptor not registered.")
+    p.selector.updateHandle(fd.SocketHandle, {Event.Read})
+
+  proc addWrite*(fd: AsyncFD, cb: Callback) =
+    let p = getGlobalDispatcher()
+    withData(p.selector, fd.SocketHandle, adata) do:
+      adata.writeCB = cb
+    do:
+      raise newException(ValueError, "File descriptor not registered.")
+    p.selector.updateHandle(fd.SocketHandle, {Event.Write})
+
+  proc poll*(timeout = 500) =
+    var keys: array[64, ReadyKey[AsyncData]]
+
+    let p = getGlobalDispatcher()
+    when supportedPlatform:
+      let customSet = {Event.Timer, Event.Signal, Event.Process,
+                       Event.Vnode, Event.User}
+
+    if p.selector.isEmpty() and p.timers.len == 0 and p.callbacks.len == 0:
+      raise newException(ValueError,
+        "No handles or timers registered in dispatcher.")
+
+    if not p.selector.isEmpty():
+      var count = p.selector.selectInto(p.adjustedTimeout(timeout), keys)
+      var i = 0
+      while i < count:
+        var update = false
+        var fd = keys[i].fd.SocketHandle
+        let events = keys[i].events
+
+        if Event.Read in events:
+          let cb = keys[i].data.readCB
+          doAssert(cb != nil)
+          if cb(fd.AsyncFD):
+            p.selector.withData(fd, adata) do:
+              if adata.readCB == cb:
+                adata.readCB = nil
+                update = true
+
+        if Event.Write in events:
+          let cb = keys[i].data.writeCB
+          doAssert(cb != nil)
+          if cb(fd.AsyncFD):
+            p.selector.withData(fd, adata) do:
+              if adata.writeCB == cb:
+                adata.writeCB = nil
+                update = true
+        
+        when supportedPlatform:
+          if (customSet * events) != {}:
+            let cb = keys[i].data.readCB
+            doAssert(cb != nil)
+            if cb(fd.AsyncFD):
+              p.selector.withData(fd, adata) do:
+                if adata.readCB == cb:
+                  adata.readCB = nil
+                  p.selector.unregister(fd)
+
+        if update:
+          var newEvents: set[Event] = {}
+          p.selector.withData(fd, adata) do:
+            if adata.readCB != nil: incl(newEvents, Event.Read)
+            if adata.writeCB != nil: incl(newEvents, Event.Write)
+          p.selector.updateHandle(fd, newEvents)
+        inc(i)
+
+    # Timer processing.
+    processTimers(p)
+    # Callback queue processing
+    processPendingCallbacks(p)
+
+  proc connect*(socket: AsyncFD, address: string, port: Port,
+    domain = AF_INET): Future[void] =
+    var retFuture = newFuture[void]("connect")
+
+    proc cb(fd: AsyncFD): bool =
+      var ret = SocketHandle(fd).getSockOptInt(cint(SOL_SOCKET), cint(SO_ERROR))
+      if ret == 0:
+          # We have connected.
+          retFuture.complete()
+          return true
+      elif ret == EINTR:
+          # interrupted, keep waiting
+          return false
+      else:
+          retFuture.fail(newException(OSError, osErrorMsg(OSErrorCode(ret))))
+          return true
+
+    assert getSockDomain(socket.SocketHandle) == domain
+    var aiList = getAddrInfo(address, port, domain)
+    var success = false
+    var lastError: OSErrorCode
+    var it = aiList
+    while it != nil:
+      var ret = connect(socket.SocketHandle, it.ai_addr, it.ai_addrlen.Socklen)
+      if ret == 0:
+        # Request to connect completed immediately.
+        success = true
+        retFuture.complete()
+        break
+      else:
+        lastError = osLastError()
+        if lastError.int32 == EINTR or lastError.int32 == EINPROGRESS:
+          success = true
+          addWrite(socket, cb)
+          break
+        else:
+          success = false
+      it = it.ai_next
+
+    dealloc(aiList)
+    if not success:
+      retFuture.fail(newException(OSError, osErrorMsg(lastError)))
+    return retFuture
+
+  proc recv*(socket: AsyncFD, size: int,
+             flags = {SocketFlag.SafeDisconn}): Future[string] =
+    var retFuture = newFuture[string]("recv")
+
+    var readBuffer = newString(size)
+
+    proc cb(sock: AsyncFD): bool =
+      result = true
+      let res = recv(sock.SocketHandle, addr readBuffer[0], size.cint,
+                     flags.toOSFlags())
+      if res < 0:
+        let lastError = osLastError()
+        if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}:
+          if flags.isDisconnectionError(lastError):
+            retFuture.complete("")
+          else:
+            retFuture.fail(newException(OSError, osErrorMsg(lastError)))
+        else:
+          result = false # We still want this callback to be called.
+      elif res == 0:
+        # Disconnected
+        retFuture.complete("")
+      else:
+        readBuffer.setLen(res)
+        retFuture.complete(readBuffer)
+    # TODO: The following causes a massive slowdown.
+    #if not cb(socket):
+    addRead(socket, cb)
+    return retFuture
+
+  proc recvInto*(socket: AsyncFD, buf: cstring, size: int,
+                 flags = {SocketFlag.SafeDisconn}): Future[int] =
+    var retFuture = newFuture[int]("recvInto")
+
+    proc cb(sock: AsyncFD): bool =
+      result = true
+      let res = recv(sock.SocketHandle, buf, size.cint,
+                     flags.toOSFlags())
+      if res < 0:
+        let lastError = osLastError()
+        if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}:
+          if flags.isDisconnectionError(lastError):
+            retFuture.complete(0)
+          else:
+            retFuture.fail(newException(OSError, osErrorMsg(lastError)))
+        else:
+          result = false # We still want this callback to be called.
+      else:
+        retFuture.complete(res)
+    # TODO: The following causes a massive slowdown.
+    #if not cb(socket):
+    addRead(socket, cb)
+    return retFuture
+
+  proc send*(socket: AsyncFD, data: string,
+             flags = {SocketFlag.SafeDisconn}): Future[void] =
+    var retFuture = newFuture[void]("send")
+
+    var written = 0
+
+    proc cb(sock: AsyncFD): bool =
+      result = true
+      let netSize = data.len-written
+      var d = data.cstring
+      let res = send(sock.SocketHandle, addr d[written], netSize.cint,
+                     MSG_NOSIGNAL)
+      if res < 0:
+        let lastError = osLastError()
+        if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}:
+          if flags.isDisconnectionError(lastError):
+            retFuture.complete()
+          else:
+            retFuture.fail(newException(OSError, osErrorMsg(lastError)))
+        else:
+          result = false # We still want this callback to be called.
+      else:
+        written.inc(res)
+        if res != netSize:
+          result = false # We still have data to send.
+        else:
+          retFuture.complete()
+    # TODO: The following causes crashes.
+    #if not cb(socket):
+    addWrite(socket, cb)
+    return retFuture
+
+  proc sendTo*(socket: AsyncFD, data: pointer, size: int, saddr: ptr SockAddr,
+               saddrLen: SockLen,
+               flags = {SocketFlag.SafeDisconn}): Future[void] =
+    ## Sends ``data`` of size ``size`` in bytes to specified destination
+    ## (``saddr`` of size ``saddrLen`` in bytes, using socket ``socket``.
+    ## The returned future will complete once all data has been sent.
+    var retFuture = newFuture[void]("sendTo")
+
+    # we will preserve address in our stack
+    var staddr: array[128, char] # SOCKADDR_STORAGE size is 128 bytes
+    var stalen = saddrLen
+    zeroMem(addr(staddr[0]), 128)
+    copyMem(addr(staddr[0]), saddr, saddrLen)
+
+    proc cb(sock: AsyncFD): bool =
+      result = true
+      let res = sendto(sock.SocketHandle, data, size, MSG_NOSIGNAL,
+                       cast[ptr SockAddr](addr(staddr[0])), stalen)
+      if res < 0:
+        let lastError = osLastError()
+        if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}:
+          retFuture.fail(newException(OSError, osErrorMsg(lastError)))
+        else:
+          result = false # We still want this callback to be called.
+      else:
+        retFuture.complete()
+
+    addWrite(socket, cb)
+    return retFuture
+
+  proc recvFromInto*(socket: AsyncFD, data: pointer, size: int,
+                     saddr: ptr SockAddr, saddrLen: ptr SockLen,
+                     flags = {SocketFlag.SafeDisconn}): Future[int] =
+    ## Receives a datagram data from ``socket`` into ``data``, which must
+    ## be at least of size ``size`` in bytes, address of datagram's sender
+    ## will be stored into ``saddr`` and ``saddrLen``. Returned future will
+    ## complete once one datagram has been received, and will return size
+    ## of packet received.
+    var retFuture = newFuture[int]("recvFromInto")
+    proc cb(sock: AsyncFD): bool =
+      result = true
+      let res = recvfrom(sock.SocketHandle, data, size.cint, flags.toOSFlags(),
+                         saddr, saddrLen)
+      if res < 0:
+        let lastError = osLastError()
+        if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}:
+          retFuture.fail(newException(OSError, osErrorMsg(lastError)))
+        else:
+          result = false
+      else:
+        retFuture.complete(res)
+    addRead(socket, cb)
+    return retFuture
+
+  proc acceptAddr*(socket: AsyncFD, flags = {SocketFlag.SafeDisconn}):
+      Future[tuple[address: string, client: AsyncFD]] =
+    var retFuture = newFuture[tuple[address: string,
+        client: AsyncFD]]("acceptAddr")
+    proc cb(sock: AsyncFD): bool =
+      result = true
+      var sockAddress: Sockaddr_storage
+      var addrLen = sizeof(sockAddress).Socklen
+      var client = accept(sock.SocketHandle,
+                          cast[ptr SockAddr](addr(sockAddress)), addr(addrLen))
+      if client == osInvalidSocket:
+        let lastError = osLastError()
+        assert lastError.int32 notin {EWOULDBLOCK, EAGAIN}
+        if lastError.int32 == EINTR:
+          return false
+        else:
+          if flags.isDisconnectionError(lastError):
+            return false
+          else:
+            retFuture.fail(newException(OSError, osErrorMsg(lastError)))
+      else:
+        register(client.AsyncFD)
+        retFuture.complete((getAddrString(cast[ptr SockAddr](addr sockAddress)),
+                            client.AsyncFD))
+    addRead(socket, cb)
+    return retFuture
+
+  when supportedPlatform:
+
+    proc addTimer*(timeout: int, oneshot: bool, cb: Callback) =
+      ## Start watching for timeout expiration, and then call the
+      ## callback ``cb``.
+      ## ``timeout`` - time in milliseconds,
+      ## ``oneshot`` - if ``true`` only one event will be dispatched,
+      ## if ``false`` continuous events every ``timeout`` milliseconds.
+      let p = getGlobalDispatcher()
+      var data = AsyncData(readCB: cb)
+      p.selector.registerTimer(timeout, oneshot, data)
+
+    proc addSignal*(signal: int, cb: Callback) =
+      ## Start watching signal ``signal``, and when signal appears, call the
+      ## callback ``cb``.
+      let p = getGlobalDispatcher()
+      var data = AsyncData(readCB: cb)
+      p.selector.registerSignal(signal, data)
+
+    proc addProcess*(pid: int, cb: Callback) =
+      ## Start watching for process exit with pid ``pid``, and then call
+      ## the callback ``cb``.
+      let p = getGlobalDispatcher()
+      var data = AsyncData(readCB: cb)
+      p.selector.registerProcess(pid, data)
+
+  proc newAsyncEvent*(): AsyncEvent =
+    ## Creates new ``AsyncEvent``.
+    result = AsyncEvent(ioselectors.newSelectEvent())
+
+  proc setEvent*(ev: AsyncEvent) =
+    ## Sets new ``AsyncEvent`` to signaled state.
+    setEvent(SelectEvent(ev))
+
+  proc close*(ev: AsyncEvent) =
+    ## Closes ``AsyncEvent``
+    close(SelectEvent(ev))
+
+  proc addEvent*(ev: AsyncEvent, cb: Callback) =
+    ## Start watching for event ``ev``, and call callback ``cb``, when
+    ## ev will be set to signaled state.
+    let p = getGlobalDispatcher()
+    var data = AsyncData(readCB: cb)
+    p.selector.registerEvent(SelectEvent(ev), data)
+
+proc sleepAsync*(ms: int): Future[void] =
+  ## Suspends the execution of the current async procedure for the next
+  ## ``ms`` milliseconds.
+  var retFuture = newFuture[void]("sleepAsync")
+  let p = getGlobalDispatcher()
+  p.timers.push((epochTime() + (ms / 1000), retFuture))
+  return retFuture
+
+proc withTimeout*[T](fut: Future[T], timeout: int): Future[bool] =
+  ## Returns a future which will complete once ``fut`` completes or after
+  ## ``timeout`` milliseconds has elapsed.
+  ##
+  ## If ``fut`` completes first the returned future will hold true,
+  ## otherwise, if ``timeout`` milliseconds has elapsed first, the returned
+  ## future will hold false.
+
+  var retFuture = newFuture[bool]("asyncdispatch.`withTimeout`")
+  var timeoutFuture = sleepAsync(timeout)
+  fut.callback =
+    proc () =
+      if not retFuture.finished: retFuture.complete(true)
+  timeoutFuture.callback =
+    proc () =
+      if not retFuture.finished: retFuture.complete(false)
+  return retFuture
+
+proc accept*(socket: AsyncFD,
+    flags = {SocketFlag.SafeDisconn}): Future[AsyncFD] =
+  ## Accepts a new connection. Returns a future containing the client socket
+  ## corresponding to that connection.
+  ## The future will complete when the connection is successfully accepted.
+  var retFut = newFuture[AsyncFD]("accept")
+  var fut = acceptAddr(socket, flags)
+  fut.callback =
+    proc (future: Future[tuple[address: string, client: AsyncFD]]) =
+      assert future.finished
+      if future.failed:
+        retFut.fail(future.error)
+      else:
+        retFut.complete(future.read.client)
+  return retFut
+
+# -- Await Macro
+
+proc skipUntilStmtList(node: NimNode): NimNode {.compileTime.} =
+  # Skips a nest of StmtList's.
+  result = node
+  if node[0].kind == nnkStmtList:
+    result = skipUntilStmtList(node[0])
+
+proc skipStmtList(node: NimNode): NimNode {.compileTime.} =
+  result = node
+  if node[0].kind == nnkStmtList:
+    result = node[0]
+
+template createCb(retFutureSym, iteratorNameSym,
+                   name: expr): stmt {.immediate.} =
+  var nameIterVar = iteratorNameSym
+  #{.push stackTrace: off.}
+  proc cb {.closure,gcsafe.} =
+    try:
+      if not nameIterVar.finished:
+        var next = nameIterVar()
+        if next == nil:
+          assert retFutureSym.finished, "Async procedure's (" &
+                 name & ") return Future was not finished."
+        else:
+          next.callback = cb
+    except:
+      if retFutureSym.finished:
+        # Take a look at tasyncexceptions for the bug which this fixes.
+        # That test explains it better than I can here.
+        raise
+      else:
+        retFutureSym.fail(getCurrentException())
+  cb()
+  #{.pop.}
+proc generateExceptionCheck(futSym,
+    tryStmt, rootReceiver, fromNode: NimNode): NimNode {.compileTime.} =
+  if tryStmt.kind == nnkNilLit:
+    result = rootReceiver
+  else:
+    var exceptionChecks: seq[tuple[cond, body: NimNode]] = @[]
+    let errorNode = newDotExpr(futSym, newIdentNode("error"))
+    for i in 1 .. <tryStmt.len:
+      let exceptBranch = tryStmt[i]
+      if exceptBranch[0].kind == nnkStmtList:
+        exceptionChecks.add((newIdentNode("true"), exceptBranch[0]))
+      else:
+        var exceptIdentCount = 0
+        var ifCond: NimNode
+        for i in 0 .. <exceptBranch.len:
+          let child = exceptBranch[i]
+          if child.kind == nnkIdent:
+            let cond = infix(errorNode, "of", child)
+            if exceptIdentCount == 0:
+              ifCond = cond
+            else:
+              ifCond = infix(ifCond, "or", cond)
+          else:
+            break
+          exceptIdentCount.inc
+
+        expectKind(exceptBranch[exceptIdentCount], nnkStmtList)
+        exceptionChecks.add((ifCond, exceptBranch[exceptIdentCount]))
+    # -> -> else: raise futSym.error
+    exceptionChecks.add((newIdentNode("true"),
+        newNimNode(nnkRaiseStmt).add(errorNode)))
+    # Read the future if there is no error.
+    # -> else: futSym.read
+    let elseNode = newNimNode(nnkElse, fromNode)
+    elseNode.add newNimNode(nnkStmtList, fromNode)
+    elseNode[0].add rootReceiver
+
+    let ifBody = newStmtList()
+    ifBody.add newCall(newIdentNode("setCurrentException"), errorNode)
+    ifBody.add newIfStmt(exceptionChecks)
+    ifBody.add newCall(newIdentNode("setCurrentException"), newNilLit())
+
+    result = newIfStmt(
+      (newDotExpr(futSym, newIdentNode("failed")), ifBody)
+    )
+    result.add elseNode
+
+template useVar(result: var NimNode, futureVarNode: NimNode, valueReceiver,
+                rootReceiver: expr, fromNode: NimNode) =
+  ## Params:
+  ##    futureVarNode: The NimNode which is a symbol identifying the Future[T]
+  ##                   variable to yield.
+  ##    fromNode: Used for better debug information (to give context).
+  ##    valueReceiver: The node which defines an expression that retrieves the
+  ##                   future's value.
+  ##
+  ##    rootReceiver: ??? TODO
+  # -> yield future<x>
+  result.add newNimNode(nnkYieldStmt, fromNode).add(futureVarNode)
+  # -> future<x>.read
+  valueReceiver = newDotExpr(futureVarNode, newIdentNode("read"))
+  result.add generateExceptionCheck(futureVarNode, tryStmt, rootReceiver,
+      fromNode)
+
+template createVar(result: var NimNode, futSymName: string,
+                   asyncProc: NimNode,
+                   valueReceiver, rootReceiver: expr,
+                   fromNode: NimNode) =
+  result = newNimNode(nnkStmtList, fromNode)
+  var futSym = genSym(nskVar, "future")
+  result.add newVarStmt(futSym, asyncProc) # -> var future<x> = y
+  useVar(result, futSym, valueReceiver, rootReceiver, fromNode)
+
+proc processBody(node, retFutureSym: NimNode,
+                 subTypeIsVoid: bool,
+                 tryStmt: NimNode): NimNode {.compileTime.} =
+  #echo(node.treeRepr)
+  result = node
+  case node.kind
+  of nnkReturnStmt:
+    result = newNimNode(nnkStmtList, node)
+    if node[0].kind == nnkEmpty:
+      if not subTypeIsVoid:
+        result.add newCall(newIdentNode("complete"), retFutureSym,
+            newIdentNode("result"))
+      else:
+        result.add newCall(newIdentNode("complete"), retFutureSym)
+    else:
+      result.add newCall(newIdentNode("complete"), retFutureSym,
+        node[0].processBody(retFutureSym, subTypeIsVoid, tryStmt))
+
+    result.add newNimNode(nnkReturnStmt, node).add(newNilLit())
+    return # Don't process the children of this return stmt
+  of nnkCommand, nnkCall:
+    if node[0].kind == nnkIdent and node[0].ident == !"await":
+      case node[1].kind
+      of nnkIdent, nnkInfix, nnkDotExpr:
+        # await x
+        # await x or y
+        result = newNimNode(nnkYieldStmt, node).add(node[1]) # -> yield x
+      of nnkCall, nnkCommand:
+        # await foo(p, x)
+        # await foo p, x
+        var futureValue: NimNode
+        result.createVar("future" & $node[1][0].toStrLit, node[1], futureValue,
+                  futureValue, node)
+      else:
+        error("Invalid node kind in 'await', got: " & $node[1].kind)
+    elif node.len > 1 and node[1].kind == nnkCommand and
+         node[1][0].kind == nnkIdent and node[1][0].ident == !"await":
+      # foo await x
+      var newCommand = node
+      result.createVar("future" & $node[0].toStrLit, node[1][1], newCommand[1],
+                newCommand, node)
+
+  of nnkVarSection, nnkLetSection:
+    case node[0][2].kind
+    of nnkCommand:
+      if node[0][2][0].kind == nnkIdent and node[0][2][0].ident == !"await":
+        # var x = await y
+        var newVarSection = node # TODO: Should this use copyNimNode?
+        result.createVar("future" & $node[0][0].ident, node[0][2][1],
+          newVarSection[0][2], newVarSection, node)
+    else: discard
+  of nnkAsgn:
+    case node[1].kind
+    of nnkCommand:
+      if node[1][0].ident == !"await":
+        # x = await y
+        var newAsgn = node
+        result.createVar("future" & $node[0].toStrLit, node[1][1], newAsgn[1], newAsgn, node)
+    else: discard
+  of nnkDiscardStmt:
+    # discard await x
+    if node[0].kind == nnkCommand and node[0][0].kind == nnkIdent and
+          node[0][0].ident == !"await":
+      var newDiscard = node
+      result.createVar("futureDiscard_" & $toStrLit(node[0][1]), node[0][1],
+                newDiscard[0], newDiscard, node)
+  of nnkTryStmt:
+    # try: await x; except: ...
+    result = newNimNode(nnkStmtList, node)
+    template wrapInTry(n, tryBody: expr) =
+      var temp = n
+      n[0] = tryBody
+      tryBody = temp
+
+      # Transform ``except`` body.
+      # TODO: Could we perform some ``await`` transformation here to get it
+      # working in ``except``?
+      tryBody[1] = processBody(n[1], retFutureSym, subTypeIsVoid, nil)
+
+    proc processForTry(n: NimNode, i: var int,
+                       res: NimNode): bool {.compileTime.} =
+      ## Transforms the body of the tryStmt. Does not transform the
+      ## body in ``except``.
+      ## Returns true if the tryStmt node was transformed into an ifStmt.
+      result = false
+      var skipped = n.skipStmtList()
+      while i < skipped.len:
+        var processed = processBody(skipped[i], retFutureSym,
+                                    subTypeIsVoid, n)
+
+        # Check if we transformed the node into an exception check.
+        # This suggests skipped[i] contains ``await``.
+        if processed.kind != skipped[i].kind or processed.len != skipped[i].len:
+          processed = processed.skipUntilStmtList()
+          expectKind(processed, nnkStmtList)
+          expectKind(processed[2][1], nnkElse)
+          i.inc
+
+          if not processForTry(n, i, processed[2][1][0]):
+            # We need to wrap the nnkElse nodes back into a tryStmt.
+            # As they are executed if an exception does not happen
+            # inside the awaited future.
+            # The following code will wrap the nodes inside the
+            # original tryStmt.
+            wrapInTry(n, processed[2][1][0])
+
+          res.add processed
+          result = true
+        else:
+          res.add skipped[i]
+          i.inc
+    var i = 0
+    if not processForTry(node, i, result):
+      # If the tryStmt hasn't been transformed we can just put the body
+      # back into it.
+      wrapInTry(node, result)
+    return
+  else: discard
+
+  for i in 0 .. <result.len:
+    result[i] = processBody(result[i], retFutureSym, subTypeIsVoid, nil)
+
+proc getName(node: NimNode): string {.compileTime.} =
+  case node.kind
+  of nnkPostfix:
+    return $node[1].ident
+  of nnkIdent:
+    return $node.ident
+  of nnkEmpty:
+    return "anonymous"
+  else:
+    error("Unknown name.")
+
+proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
+  ## This macro transforms a single procedure into a closure iterator.
+  ## The ``async`` macro supports a stmtList holding multiple async procedures.
+  if prc.kind notin {nnkProcDef, nnkLambda}:
+      error("Cannot transform this node kind into an async proc." &
+            " Proc definition or lambda node expected.")
+
+  hint("Processing " & prc[0].getName & " as an async proc.")
+
+  let returnType = prc[3][0]
+  var baseType: NimNode
+  # Verify that the return type is a Future[T]
+  if returnType.kind == nnkBracketExpr:
+    let fut = repr(returnType[0])
+    if fut != "Future":
+      error("Expected return type of 'Future' got '" & fut & "'")
+    baseType = returnType[1]
+  elif returnType.kind in nnkCallKinds and $returnType[0] == "[]":
+    let fut = repr(returnType[1])
+    if fut != "Future":
+      error("Expected return type of 'Future' got '" & fut & "'")
+    baseType = returnType[2]
+  elif returnType.kind == nnkEmpty:
+    baseType = returnType
+  else:
+    error("Expected return type of 'Future' got '" & repr(returnType) & "'")
+
+  let subtypeIsVoid = returnType.kind == nnkEmpty or
+        (baseType.kind == nnkIdent and returnType[1].ident == !"void")
+
+  var outerProcBody = newNimNode(nnkStmtList, prc[6])
+
+  # -> var retFuture = newFuture[T]()
+  var retFutureSym = genSym(nskVar, "retFuture")
+  var subRetType =
+    if returnType.kind == nnkEmpty: newIdentNode("void")
+    else: baseType
+  outerProcBody.add(
+    newVarStmt(retFutureSym,
+      newCall(
+        newNimNode(nnkBracketExpr, prc[6]).add(
+          newIdentNode(!"newFuture"), # TODO: Strange bug here? Remove the `!`.
+          subRetType),
+      newLit(prc[0].getName)))) # Get type from return type of this proc
+
+  # -> iterator nameIter(): FutureBase {.closure.} =
+  # ->   {.push warning[resultshadowed]: off.}
+  # ->   var result: T
+  # ->   {.pop.}
+  # ->   <proc_body>
+  # ->   complete(retFuture, result)
+  var iteratorNameSym = genSym(nskIterator, $prc[0].getName & "Iter")
+  var procBody = prc[6].processBody(retFutureSym, subtypeIsVoid, nil)
+  if not subtypeIsVoid:
+    procBody.insert(0, newNimNode(nnkPragma).add(newIdentNode("push"),
+      newNimNode(nnkExprColonExpr).add(newNimNode(nnkBracketExpr).add(
+        newIdentNode("warning"), newIdentNode("resultshadowed")),
+      newIdentNode("off")))) # -> {.push warning[resultshadowed]: off.}
+
+    procBody.insert(1, newNimNode(nnkVarSection, prc[6]).add(
+      newIdentDefs(newIdentNode("result"), baseType))) # -> var result: T
+
+    procBody.insert(2, newNimNode(nnkPragma).add(
+      newIdentNode("pop"))) # -> {.pop.})
+
+    procBody.add(
+      newCall(newIdentNode("complete"),
+        retFutureSym, newIdentNode("result"))) # -> complete(retFuture, result)
+  else:
+    # -> complete(retFuture)
+    procBody.add(newCall(newIdentNode("complete"), retFutureSym))
+
+  var closureIterator = newProc(iteratorNameSym, [newIdentNode("FutureBase")],
+                                procBody, nnkIteratorDef)
+  closureIterator[4] = newNimNode(nnkPragma, prc[6]).add(newIdentNode("closure"))
+  outerProcBody.add(closureIterator)
+
+  # -> createCb(retFuture)
+  #var cbName = newIdentNode("cb")
+  var procCb = newCall(bindSym"createCb", retFutureSym, iteratorNameSym,
+                       newStrLitNode(prc[0].getName))
+  outerProcBody.add procCb
+
+  # -> return retFuture
+  outerProcBody.add newNimNode(nnkReturnStmt, prc[6][prc[6].len-1]).add(retFutureSym)
+
+  result = prc
+
+  # Remove the 'async' pragma.
+  for i in 0 .. <result[4].len:
+    if result[4][i].kind == nnkIdent and result[4][i].ident == !"async":
+      result[4].del(i)
+  result[4] = newEmptyNode()
+  if subtypeIsVoid:
+    # Add discardable pragma.
+    if returnType.kind == nnkEmpty:
+      # Add Future[void]
+      result[3][0] = parseExpr("Future[void]")
+
+  result[6] = outerProcBody
+
+  #echo(treeRepr(result))
+  #if prc[0].getName == "testInfix":
+  #  echo(toStrLit(result))
+
+macro async*(prc: stmt): stmt {.immediate.} =
+  ## Macro which processes async procedures into the appropriate
+  ## iterators and yield statements.
+  if prc.kind == nnkStmtList:
+    for oneProc in prc:
+      result = newStmtList()
+      result.add asyncSingleProc(oneProc)
+  else:
+    result = asyncSingleProc(prc)
+
+proc recvLine*(socket: AsyncFD): Future[string] {.async.} =
+  ## Reads a line of data from ``socket``. Returned future will complete once
+  ## a full line is read or an error occurs.
+  ##
+  ## If a full line is read ``\r\L`` is not
+  ## added to ``line``, however if solely ``\r\L`` is read then ``line``
+  ## will be set to it.
+  ##
+  ## If the socket is disconnected, ``line`` will be set to ``""``.
+  ##
+  ## If the socket is disconnected in the middle of a line (before ``\r\L``
+  ## is read) then line will be set to ``""``.
+  ## The partial line **will be lost**.
+  ##
+  ## **Warning**: This assumes that lines are delimited by ``\r\L``.
+  ##
+  ## **Note**: This procedure is mostly used for testing. You likely want to
+  ## use ``asyncnet.recvLine`` instead.
+
+  template addNLIfEmpty(): stmt =
+    if result.len == 0:
+      result.add("\c\L")
+
+  result = ""
+  var c = ""
+  while true:
+    c = await recv(socket, 1)
+    if c.len == 0:
+      return ""
+    if c == "\r":
+      c = await recv(socket, 1)
+      assert c == "\l"
+      addNLIfEmpty()
+      return
+    elif c == "\L":
+      addNLIfEmpty()
+      return
+    add(result, c)
+
+proc callSoon*(cbproc: proc ()) =
+  ## Schedule `cbproc` to be called as soon as possible.
+  ## The callback is called when control returns to the event loop.
+  getGlobalDispatcher().callbacks.enqueue(cbproc)
+
+proc runForever*() =
+  ## Begins a never ending global dispatcher poll loop.
+  while true:
+    poll()
+
+proc waitFor*[T](fut: Future[T]): T =
+  ## **Blocks** the current thread until the specified future completes.
+  while not fut.finished:
+    poll()
+
+  fut.read