summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--lib/posix/kqueue.nim71
-rw-r--r--lib/pure/selectors.nim94
2 files changed, 155 insertions, 10 deletions
diff --git a/lib/posix/kqueue.nim b/lib/posix/kqueue.nim
new file mode 100644
index 000000000..511ada9ac
--- /dev/null
+++ b/lib/posix/kqueue.nim
@@ -0,0 +1,71 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2015 Adam Strzelecki
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+{.deadCodeElim:on.}
+
+from posix import Timespec
+
+# Filters:
+const
+  EVFILT_READ*     = -1
+  EVFILT_WRITE*    = -2
+  EVFILT_AIO*      = -3
+  EVFILT_VNODE*    = -4
+  EVFILT_PROC*     = -5
+  EVFILT_SIGNAL*   = -6
+  EVFILT_TIMER*    = -7
+  EVFILT_MACHPORT* = -8
+  EVFILT_FS*       = -9
+  EVFILT_USER*     = -10
+  # -11 is unused
+  EVFILT_VM*       = -12
+
+# Actions:
+const
+  EV_ADD*      = 0x0001 ## Add event to queue (implies enable).
+                        ## Re-adding an existing element modifies it.
+  EV_DELETE*   = 0x0002 ## Delete event from queue.
+  EV_ENABLE*   = 0x0004 ## Enable event.
+  EV_DISABLE*  = 0x0008 ## Disable event (not reported).
+
+# Flags:
+const
+  EV_ONESHOT*  = 0x0010 ## Only report one occurrence.
+  EV_CLEAR*    = 0x0020 ## Clear event state after reporting.
+  EV_RECEIPT*  = 0x0040 ## Force EV_ERROR on success, data == 0
+  EV_DISPATCH* = 0x0080 ## Disable event after reporting.
+
+# Return values:
+const
+  EV_EOF*      = 0x8000 ## EOF detected
+  EV_ERROR*    = 0x4000 ## Error, data contains errno
+
+type
+  KEvent* {.importc: "struct kevent",
+            header: "<sys/event.h>", pure, final.} = object
+    ident*: cuint    ## identifier for this event  (uintptr_t)
+    filter*: cshort  ## filter for event
+    flags*: cushort  ## general flags
+    fflags*: cuint   ## filter-specific flags
+    data*: cuint     ## filter-specific data  (intptr_t)
+    #udata*: ptr void ## opaque user data identifier
+
+proc kqueue*(): cint {.importc: "kqueue", header: "<sys/event.h>".}
+  ## Creates new queue and returns its descriptor.
+
+proc kevent*(kqFD: cint,
+             changelist: ptr KEvent, nchanges: cint,
+             eventlist: ptr KEvent, nevents: cint, timeout: ptr Timespec): cint
+     {.importc: "kevent", header: "<sys/event.h>".}
+  ## Manipulates queue for given ``kqFD`` descriptor.
+
+proc EV_SET*(event: ptr KEvent, ident: cuint, filter: cshort, flags: cushort,
+             fflags: cuint, data: cuint, udata: ptr void)
+     {.importc: "EV_SET", header: "<sys/event.h>".}
+  ## Fills event with given data.
diff --git a/lib/pure/selectors.nim b/lib/pure/selectors.nim
index bfc393a96..ca969c761 100644
--- a/lib/pure/selectors.nim
+++ b/lib/pure/selectors.nim
@@ -13,6 +13,8 @@ import os, unsigned, hashes
 
 when defined(linux):
   import posix, epoll
+elif defined(macosx) or defined(freebsd) or defined(openbsd) or defined(netbsd):
+  import posix, kqueue, times
 elif defined(windows):
   import winlean
 else:
@@ -79,7 +81,6 @@ when defined(nimdoc):
   proc `[]`*(s: Selector, fd: SocketHandle): SelectorKey =
     ## Retrieves the selector key for ``fd``.
 
-
 elif defined(linux):
   type
     Selector* = object
@@ -99,15 +100,13 @@ elif defined(linux):
     result.data.fd = fd.cint
 
   proc register*(s: var Selector, fd: SocketHandle, events: set[Event],
-      data: SelectorData) =
+                 data: SelectorData) =
     var event = createEventStruct(events, fd)
     if events != {}:
       if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0:
         raiseOSError(osLastError())
 
-    var key = SelectorKey(fd: fd, events: events, data: data)
-
-    s.fds[fd] = key
+    s.fds[fd] = SelectorKey(fd: fd, events: events, data: data)
 
   proc update*(s: var Selector, fd: SocketHandle, events: set[Event]) =
     if s.fds[fd].events != events:
@@ -154,11 +153,6 @@ elif defined(linux):
       raiseOSError(err)
 
   proc select*(s: var Selector, timeout: int): seq[ReadyInfo] =
-    ##
-    ## The ``events`` field of the returned ``key`` contains the original events
-    ## for which the ``fd`` was bound. This is contrary to the ``events`` field
-    ## of the ``TReadyInfo`` tuple which determines which events are ready
-    ## on the ``fd``.
     result = @[]
     let evNum = epoll_wait(s.epollFD, addr s.events[0], 64.cint, timeout.cint)
     if evNum < 0:
@@ -204,6 +198,86 @@ elif defined(linux):
     ## Retrieves the selector key for ``fd``.
     return s.fds[fd]
 
+elif defined(macosx) or defined(freebsd) or defined(openbsd) or defined(netbsd):
+  type
+    Selector* = object
+      kqFD: cint
+      events: array[64, KEvent]
+      when MultiThreaded:
+        fds: SharedTable[SocketHandle, SelectorKey]
+      else:
+        fds: Table[SocketHandle, SelectorKey]
+
+  template modifyKQueue(kqFD: cint, fd: SocketHandle, event: Event,
+                        op: cushort) =
+    var kev = KEvent(ident:  fd.cuint,
+                     filter: if event == EvRead: EVFILT_READ else: EVFILT_WRITE,
+                     flags:  op)
+    if kevent(kqFD, addr kev, 1, nil, 0, nil) == -1:
+      raiseOSError(osLastError())
+
+  proc register*(s: var Selector, fd: SocketHandle, events: set[Event],
+                 data: SelectorData) =
+    for event in events:
+      modifyKQueue(s.kqFD, fd, event, EV_ADD)
+    s.fds[fd] = SelectorKey(fd: fd, events: events, data: data)
+
+  proc update*(s: var Selector, fd: SocketHandle, events: set[Event]) =
+    let previousEvents = s.fds[fd].events
+    if previousEvents != events:
+      for event in events-previousEvents:
+        modifyKQueue(s.kqFD, fd, event, EV_ADD)
+      for event in previousEvents-events:
+        modifyKQueue(s.kqFD, fd, event, EV_DELETE)
+      s.fds.mget(fd).events = events
+
+  proc unregister*(s: var Selector, fd: SocketHandle) =
+    for event in s.fds[fd].events:
+      modifyKQueue(s.kqFD, fd, event, EV_DELETE)
+    s.fds.del(fd)
+
+  proc close*(s: var Selector) =
+    when MultiThreaded: deinitSharedTable(s.fds)
+    if s.kqFD.close() != 0: raiseOSError(osLastError())
+
+  proc select*(s: var Selector, timeout: int): seq[ReadyInfo] =
+    result = @[]
+    var tv = Timespec(tv_sec: timeout.Time, tv_nsec: 0)
+    let evNum = kevent(s.kqFD, nil, 0, addr s.events[0], 64.cint, addr tv)
+    if evNum < 0:
+      let err = osLastError()
+      if err.cint == EINTR:
+        return @[]
+      raiseOSError(err)
+    if evNum == 0: return @[]
+    for i in 0 .. <evNum:
+      let fd = s.events[i].ident.SocketHandle
+
+      var evSet: set[Event] = {}
+      if  (s.events[i].flags and EV_EOF) != 0: evSet = evSet + {EvError}
+      if   s.events[i].filter == EVFILT_READ:  evSet = evSet + {EvRead}
+      elif s.events[i].filter == EVFILT_WRITE: evSet = evSet + {EvWrite}
+      let selectorKey = s.fds[fd]
+      assert selectorKey.fd != 0.SocketHandle
+      result.add((selectorKey, evSet))
+
+  proc newSelector*(): Selector =
+    result.kqFD = kqueue()
+    if result.kqFD < 0:
+      raiseOSError(osLastError())
+    when MultiThreaded:
+      result.fds = initSharedTable[SocketHandle, SelectorKey]()
+    else:
+      result.fds = initTable[SocketHandle, SelectorKey]()
+
+  proc contains*(s: Selector, fd: SocketHandle): bool =
+    ## Determines whether selector contains a file descriptor.
+    s.fds.hasKey(fd) # and s.fds[fd].events != {}
+
+  proc `[]`*(s: Selector, fd: SocketHandle): SelectorKey =
+    ## Retrieves the selector key for ``fd``.
+    return s.fds[fd]
+
 elif not defined(nimdoc):
   # TODO: kqueue for bsd/mac os x.
   type