summary refs log tree commit diff stats
path: root/lib/pure/selectors.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/selectors.nim')
-rw-r--r--lib/pure/selectors.nim355
1 files changed, 178 insertions, 177 deletions
diff --git a/lib/pure/selectors.nim b/lib/pure/selectors.nim
index 83c158da1..6482a01a6 100644
--- a/lib/pure/selectors.nim
+++ b/lib/pure/selectors.nim
@@ -1,7 +1,7 @@
 #
 #
 #            Nimrod's Runtime Library
-#        (c) Copyright 2013 Dominik Picheta
+#        (c) Copyright 2014 Dominik Picheta
 #
 #    See the file "copying.txt", included in this
 #    distribution, for details about the copyright.
@@ -9,212 +9,211 @@
 
 # TODO: Docs.
 
-import tables, os, unsigned
-when defined(windows):
-  import winlean
-else:
-  import posix
+import tables, os, unsigned, hashes
+
+when defined(linux): import posix, epoll
+elif defined(windows): import winlean
+
+proc hash*(x: TSocketHandle): THash {.borrow.}
 
 type
   TEvent* = enum
     EvRead, EvWrite
 
-  TSelectorKey* = object
-    fd: cint
-    events: set[TEvent]
-    data: PObject
-
-  TReadyInfo* = tuple[key: TSelectorKey, events: set[TEvent]]
-
-  PSelector* = ref object of PObject ## Selector interface.
-    fds*: TTable[cint, TSelectorKey]
-    registerImpl*: proc (s: PSelector, fd: cint, events: set[TEvent],
-                    data: PObject): TSelectorKey {.nimcall, tags: [FWriteIO].}
-    unregisterImpl*: proc (s: PSelector, fd: cint): TSelectorKey {.nimcall, tags: [FWriteIO].}
-    selectImpl*: proc (s: PSelector, timeout: int): seq[TReadyInfo] {.nimcall, tags: [FReadIO].}
-    closeImpl*: proc (s: PSelector) {.nimcall.}
-
-template initSelector(r: expr) =
-  new r
-  r.fds = initTable[cint, TSelectorKey]()
-
-proc register*(s: PSelector, fd: cint, events: set[TEvent], data: PObject):
-    TSelectorKey =
-  if not s.registerImpl.isNil: result = s.registerImpl(s, fd, events, data)
-
-proc unregister*(s: PSelector, fd: cint): TSelectorKey =
-  ##
-  ## **Note:** For the ``epoll`` implementation the resulting ``TSelectorKey``
-  ## will only have the ``fd`` field set. This is an optimisation and may
-  ## change in the future if a viable use case is presented. 
-  if not s.unregisterImpl.isNil: result = s.unregisterImpl(s, fd)
-
-proc select*(s: PSelector, timeout = 500): seq[TReadyInfo] =
-  ##
-  ## 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``.
-
-  if not s.selectImpl.isNil: result = s.selectImpl(s, timeout)
+  PSelectorKey* = ref object
+    fd*: TSocketHandle
+    events*: set[TEvent] ## The events which ``fd`` listens for.
+    data*: PObject ## User object.
 
-proc close*(s: PSelector) =
-  if not s.closeImpl.isNil: s.closeImpl(s)
+  TReadyInfo* = tuple[key: PSelectorKey, events: set[TEvent]]
 
-# ---- Select() ----------------------------------------------------------------
-
-type
-  PSelectSelector* = ref object of PSelector ## Implementation of select()
-
-proc ssRegister(s: PSelector, fd: cint, events: set[TEvent],
-    data: PObject): TSelectorKey =
-  if s.fds.hasKey(fd):
-    raise newException(EInvalidValue, "FD already exists in selector.")
-  var sk = TSelectorKey(fd: fd, events: events, data: data)
-  s.fds[fd] = sk
-  result = sk
-
-proc ssUnregister(s: PSelector, fd: cint): TSelectorKey =
-  result = s.fds[fd]
-  s.fds.del(fd)
-
-proc ssClose(s: PSelector) = nil
-
-proc timeValFromMilliseconds(timeout: int): TTimeVal =
-  if timeout != -1:
-    var seconds = timeout div 1000
-    result.tv_sec = seconds.int32
-    result.tv_usec = ((timeout - seconds * 1000) * 1000).int32
-
-proc createFdSet(rd, wr: var TFdSet, fds: TTable[cint, TSelectorKey],
-    m: var int) =
-  FD_ZERO(rd); FD_ZERO(wr)
-  for k, v in pairs(fds):
-    if EvRead in v.events: 
-      m = max(m, int(k))
-      FD_SET(k, rd)
-    if EvWrite in v.events:
-      m = max(m, int(k))
-      FD_SET(k, wr)
-   
-proc getReadyFDs(rd, wr: var TFdSet, fds: TTable[cint, TSelectorKey]):
-    seq[TReadyInfo] =
-  result = @[]
-  for k, v in pairs(fds):
-    var events: set[TEvent] = {}
-    if FD_ISSET(k, rd) != 0'i32:
-      events = events + {EvRead}
-    if FD_ISSET(k, wr) != 0'i32:
-      events = events + {EvWrite}
-    result.add((v, events))
-
-proc select(fds: TTable[cint, TSelectorKey], timeout = 500):
-  seq[TReadyInfo] =
-  var tv {.noInit.}: TTimeVal = timeValFromMilliseconds(timeout)
-  
-  var rd, wr: TFdSet
-  var m = 0
-  createFdSet(rd, wr, fds, m)
-  
-  var retCode = 0
-  if timeout != -1:
-    retCode = int(select(cint(m+1), addr(rd), addr(wr), nil, addr(tv)))
-  else:
-    retCode = int(select(cint(m+1), addr(rd), addr(wr), nil, nil))
-  
-  if retCode < 0:
-    OSError(OSLastError())
-  elif retCode == 0:
-    return @[]
-  else:
-    return getReadyFDs(rd, wr, fds)
-
-proc ssSelect(s: PSelector, timeout: int): seq[TReadyInfo] =
-  result = select(s.fds, timeout)
-
-proc newSelectSelector*(): PSelectSelector =
-  initSelector(result)
-  result.registerImpl = ssRegister
-  result.unregisterImpl = ssUnregister
-  result.selectImpl = ssSelect
-  result.closeImpl = ssClose
-
-# ---- Epoll -------------------------------------------------------------------
-
-when defined(linux):
-  import epoll
+when defined(linux) or defined(nimdoc):
   type
-    PEpollSelector* = ref object of PSelector
+    PSelector* = ref object
       epollFD: cint
       events: array[64, ptr epoll_event]
+      fds: TTable[TSocketHandle, PSelectorKey]
   
-    TDataWrapper = object
-      fd: cint
-      boundEvents: set[TEvent] ## The events which ``fd`` listens for.
-      data: PObject ## User object.
-  
-  proc esRegister(s: PSelector, fd: cint, events: set[TEvent],
-      data: PObject): TSelectorKey =
-    var es = PEpollSelector(s)
-    var event: epoll_event
+  proc createEventStruct(events: set[TEvent], fd: TSocketHandle): epoll_event =
     if EvRead in events:
-      event.events = EPOLLIN
+      result.events = EPOLLIN
     if EvWrite in events:
-      event.events = event.events or EPOLLOUT
-    
-    var dw = cast[ptr TDataWrapper](alloc0(sizeof(TDataWrapper))) # TODO: This needs to be dealloc'd
-    dw.fd = fd
-    dw.boundEvents = events
-    dw.data = data
-    event.data.thePtr = dw
+      result.events = result.events or EPOLLOUT
+    result.data.fd = fd.cint
+  
+  proc register*(s: PSelector, fd: TSocketHandle, events: set[TEvent],
+      data: PObject): PSelectorKey {.discardable.} =
+    ## Registers file descriptor ``fd`` to selector ``s`` with a set of TEvent
+    ## ``events``.
+    if s.fds.hasKey(fd):
+      raise newException(EInvalidValue, "File descriptor already exists.")
     
-    if epoll_ctl(es.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0:
+    var event = createEventStruct(events, fd)
+  
+    if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0:
       OSError(OSLastError())
-    
-    result = TSelectorKey(fd: fd, events: events, data: data)
   
-  proc esUnregister(s: PSelector, fd: cint): TSelectorKey =
-    # We cannot find out the information about this ``fd`` from the epoll
-    # context. As such I will simply return an almost empty TSelectorKey.
-    var es = PEpollSelector(s)
-    if epoll_ctl(es.epollFD, EPOLL_CTL_DEL, fd, nil) != 0:
+    var key = PSelectorKey(fd: fd, events: events, data: data)
+  
+    s.fds[fd] = key
+    result = key
+  
+  proc update*(s: PSelector, fd: TSocketHandle,
+      events: set[TEvent]): PSelectorKey {.discardable.} =
+    ## Updates the events which ``fd`` wants notifications for.
+    if not s.fds.hasKey(fd):
+      raise newException(EInvalidValue, "File descriptor not found.")
+    var event = createEventStruct(events, fd)
+    
+    s.fds[fd].events = events
+    echo("About to update")
+    if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fd, addr(event)) != 0:
       OSError(OSLastError())
-    # We could fill in the ``fds`` TTable to get the info, but that wouldn't
-    # be nice for our memory.
-    result = TSelectorKey(fd: fd, events: {}, data: nil)
-
-  proc esClose(s: PSelector) =
-    var es = PEpollSelector(s)
-    if es.epollFD.close() != 0: OSError(OSLastError())
-    dealloc(addr es.events) # TODO: Test this
+    echo("finished updating")
+    result = s.fds[fd]
   
-  proc esSelect(s: PSelector, timeout: int): seq[TReadyInfo] =
+  proc unregister*(s: PSelector, fd: TSocketHandle): PSelectorKey {.discardable.} =
+    if not s.fds.hasKey(fd):
+      raise newException(EInvalidValue, "File descriptor not found.")
+    if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fd, nil) != 0:
+      OSError(OSLastError())
+    result = s.fds[fd]
+    s.fds.del(fd)
+
+  proc close*(s: PSelector) =
+    if s.epollFD.close() != 0: OSError(OSLastError())
+    dealloc(addr s.events) # TODO: Test this
+  
+  proc select*(s: PSelector, timeout: int): seq[TReadyInfo] =
+    ##
+    ## 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 = @[]
-    var es = PEpollSelector(s)
     
-    let evNum = epoll_wait(es.epollFD, es.events[0], 64.cint, timeout.cint)
+    let evNum = epoll_wait(s.epollFD, s.events[0], 64.cint, timeout.cint)
     if evNum < 0: OSError(OSLastError())
     if evNum == 0: return @[]
     for i in 0 .. <evNum:
       var evSet: set[TEvent] = {}
-      if (es.events[i].events and EPOLLIN) != 0: evSet = evSet + {EvRead}
-      if (es.events[i].events and EPOLLOUT) != 0: evSet = evSet + {EvWrite}
-      let dw = cast[ptr TDataWrapper](es.events[i].data.thePtr)
+      if (s.events[i].events and EPOLLIN) != 0: evSet = evSet + {EvRead}
+      if (s.events[i].events and EPOLLOUT) != 0: evSet = evSet + {EvWrite}
       
-      let selectorKey = TSelectorKey(fd: dw.fd, events: dw.boundEvents, 
-          data: dw.data)
+      let selectorKey = s.fds[s.events[i].data.fd.TSocketHandle]
       result.add((selectorKey, evSet))
   
-  proc newEpollSelector*(): PEpollSelector =
+  proc newSelector*(): PSelector =
     new result
     result.epollFD = epoll_create(64)
     result.events = cast[array[64, ptr epoll_event]](alloc0(sizeof(epoll_event)*64))
+    result.fds = initTable[TSocketHandle, PSelectorKey]()
     if result.epollFD < 0:
       OSError(OSLastError())
-    result.registerImpl = esRegister
-    result.unregisterImpl = esUnregister
-    result.closeImpl = esClose
-    result.selectImpl = esSelect
+
+  proc contains*(s: PSelector, fd: TSocketHandle): bool =
+    ## Determines whether selector contains a file descriptor.
+    return s.fds.hasKey(fd)
+
+  proc `[]`*(s: PSelector, fd: TSocketHandle): PSelectorKey =
+    ## Retrieves the selector key for ``fd``.
+    return s.fds[fd]
+
+elif defined(windows):
+  type
+    PSelector* = ref object
+      fds: TTable[TSocketHandle, PSelectorKey]
+
+  proc register*(s: PSelector, fd: TSocketHandle, events: set[TEvent],
+      data: PObject): PSelectorKey {.discardable.} =
+    if s.fds.hasKey(fd):
+      raise newException(EInvalidValue, "File descriptor already exists.")
+    var sk = PSelectorKey(fd: fd, events: events, data: data)
+    s.fds[fd] = sk
+    result = sk
+
+  proc update*(s: PSelector, fd: TSocketHandle,
+      events: set[TEvent]): PSelectorKey {.discardable.} =
+    ## Updates the events which ``fd`` wants notifications for.
+    if not s.fds.hasKey(fd):
+      raise newException(EInvalidValue, "File descriptor not found.")
+
+    s.fds[fd].events = events
+    result = s.fds[fd]
+
+  proc unregister*(s: PSelector, fd: TSocketHandle): PSelectorKey {.discardable.} =
+    result = s.fds[fd]
+    s.fds.del(fd)
+
+  proc close*(s: PSelector) = nil
+
+  proc timeValFromMilliseconds(timeout: int): TTimeVal =
+    if timeout != -1:
+      var seconds = timeout div 1000
+      result.tv_sec = seconds.int32
+      result.tv_usec = ((timeout - seconds * 1000) * 1000).int32
+
+  proc createFdSet(rd, wr: var TFdSet, fds: TTable[TSocketHandle, PSelectorKey],
+      m: var int) =
+    FD_ZERO(rd); FD_ZERO(wr)
+    for k, v in pairs(fds):
+      if EvRead in v.events: 
+        m = max(m, int(k))
+        FD_SET(k, rd)
+      if EvWrite in v.events:
+        m = max(m, int(k))
+        FD_SET(k, wr)
+     
+  proc getReadyFDs(rd, wr: var TFdSet, fds: TTable[TSocketHandle, PSelectorKey]):
+      seq[TReadyInfo] =
+    result = @[]
+    for k, v in pairs(fds):
+      var events: set[TEvent] = {}
+      if FD_ISSET(k, rd) != 0'i32:
+        events = events + {EvRead}
+      if FD_ISSET(k, wr) != 0'i32:
+        events = events + {EvWrite}
+      result.add((v, events))
+
+  proc select(fds: TTable[TSocketHandle, PSelectorKey], timeout = 500):
+    seq[TReadyInfo] =
+    var tv {.noInit.}: TTimeVal = timeValFromMilliseconds(timeout)
+    
+    var rd, wr: TFdSet
+    var m = 0
+    createFdSet(rd, wr, fds, m)
+    
+    var retCode = 0
+    if timeout != -1:
+      retCode = int(select(TSocketHandle(m+1), addr(rd), addr(wr), nil, addr(tv)))
+    else:
+      retCode = int(select(TSocketHandle(m+1), addr(rd), addr(wr), nil, nil))
+    
+    if retCode < 0:
+      OSError(OSLastError())
+    elif retCode == 0:
+      return @[]
+    else:
+      return getReadyFDs(rd, wr, fds)
+
+  proc select*(s: PSelector, timeout: int): seq[TReadyInfo] =
+    result = select(s.fds, timeout)
+
+  proc newSelector*(): PSelector =
+    new result
+    result.fds = initTable[TSocketHandle, PSelectorKey]()
+
+  proc contains*(s: PSelector, fd: TSocketHandle): bool =
+    return s.fds.hasKey(fd)
+
+  proc `[]`*(s: PSelector, fd: TSocketHandle): PSelectorKey =
+    return s.fds[fd]
+
+elif defined(bsd) or defined(macosx):
+  # TODO: kqueue
+  {.error: "Sorry your platform is not supported yet.".}
+else:
+  {.error: "Sorry your platform is not supported.".}
 
 when isMainModule:
   # Select()
@@ -224,11 +223,12 @@ when isMainModule:
       sock: TSocket
   
   var sock = socket()
+  sock.setBlocking(false)
   sock.connect("irc.freenode.net", TPort(6667))
   
-  var selector = newEpollSelector()
+  var selector = newSelector()
   var data = PSockWrapper(sock: sock)
-  let key = selector.register(sock.getFD.cint, {EvRead}, data)
+  let key = selector.register(sock.getFD, {EvWrite}, data)
   var i = 0
   while true:
     let ready = selector.select(1000)
@@ -236,6 +236,7 @@ when isMainModule:
     if ready.len > 0: echo ready[0].events
     i.inc
     if i == 6:
+      assert selector.unregister(sock.getFD).fd == sock.getFD
       selector.close()
       break