selectors.nim 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2016 Eugene Kabanov
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module allows high-level and efficient I/O multiplexing.
  10. ##
  11. ## Supported OS primitives: ``epoll``, ``kqueue``, ``poll`` and
  12. ## Windows ``select``.
  13. ##
  14. ## To use threadsafe version of this module, it needs to be compiled
  15. ## with both ``-d:threadsafe`` and ``--threads:on`` options.
  16. ##
  17. ## Supported features: files, sockets, pipes, timers, processes, signals
  18. ## and user events.
  19. ##
  20. ## Fully supported OS: MacOSX, FreeBSD, OpenBSD, NetBSD, Linux (except
  21. ## for Android).
  22. ##
  23. ## Partially supported OS: Windows (only sockets and user events),
  24. ## Solaris (files, sockets, handles and user events).
  25. ## Android (files, sockets, handles and user events).
  26. ##
  27. ## TODO: ``/dev/poll``, ``event ports`` and filesystem events.
  28. import os, nativesockets
  29. const hasThreadSupport = compileOption("threads") and defined(threadsafe)
  30. const ioselSupportedPlatform* = defined(macosx) or defined(freebsd) or
  31. defined(netbsd) or defined(openbsd) or
  32. defined(dragonfly) or
  33. (defined(linux) and not defined(android) and not defined(emscripten))
  34. ## This constant is used to determine whether the destination platform is
  35. ## fully supported by ``ioselectors`` module.
  36. const bsdPlatform = defined(macosx) or defined(freebsd) or
  37. defined(netbsd) or defined(openbsd) or
  38. defined(dragonfly)
  39. when defined(nimdoc):
  40. type
  41. Selector*[T] = ref object
  42. ## An object which holds descriptors to be checked for read/write status
  43. IOSelectorsException* = object of CatchableError
  44. ## Exception that is raised if an IOSelectors error occurs.
  45. Event* {.pure.} = enum
  46. ## An enum which hold event types
  47. Read, ## Descriptor is available for read
  48. Write, ## Descriptor is available for write
  49. Timer, ## Timer descriptor is completed
  50. Signal, ## Signal is raised
  51. Process, ## Process is finished
  52. Vnode, ## BSD specific file change
  53. User, ## User event is raised
  54. Error, ## Error occurred while waiting for descriptor
  55. VnodeWrite, ## NOTE_WRITE (BSD specific, write to file occurred)
  56. VnodeDelete, ## NOTE_DELETE (BSD specific, unlink of file occurred)
  57. VnodeExtend, ## NOTE_EXTEND (BSD specific, file extended)
  58. VnodeAttrib, ## NOTE_ATTRIB (BSD specific, file attributes changed)
  59. VnodeLink, ## NOTE_LINK (BSD specific, file link count changed)
  60. VnodeRename, ## NOTE_RENAME (BSD specific, file renamed)
  61. VnodeRevoke ## NOTE_REVOKE (BSD specific, file revoke occurred)
  62. ReadyKey* = object
  63. ## An object which holds result for descriptor
  64. fd* : int ## file/socket descriptor
  65. events*: set[Event] ## set of events
  66. errorCode*: OSErrorCode ## additional error code information for
  67. ## Error events
  68. SelectEvent* = object
  69. ## An object which holds user defined event
  70. proc newSelector*[T](): Selector[T] =
  71. ## Creates a new selector
  72. proc close*[T](s: Selector[T]) =
  73. ## Closes the selector.
  74. proc registerHandle*[T](s: Selector[T], fd: int | SocketHandle,
  75. events: set[Event], data: T) =
  76. ## Registers file/socket descriptor ``fd`` to selector ``s``
  77. ## with events set in ``events``. The ``data`` is application-defined
  78. ## data, which will be passed when an event is triggered.
  79. proc updateHandle*[T](s: Selector[T], fd: int | SocketHandle,
  80. events: set[Event]) =
  81. ## Update file/socket descriptor ``fd``, registered in selector
  82. ## ``s`` with new events set ``event``.
  83. proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool,
  84. data: T): int {.discardable.} =
  85. ## Registers timer notification with ``timeout`` (in milliseconds)
  86. ## to selector ``s``.
  87. ##
  88. ## If ``oneshot`` is ``true``, timer will be notified only once.
  89. ##
  90. ## Set ``oneshot`` to ``false`` if you want periodic notifications.
  91. ##
  92. ## The ``data`` is application-defined data, which will be passed, when
  93. ## the timer is triggered.
  94. ##
  95. ## Returns the file descriptor for the registered timer.
  96. proc registerSignal*[T](s: Selector[T], signal: int,
  97. data: T): int {.discardable.} =
  98. ## Registers Unix signal notification with ``signal`` to selector
  99. ## ``s``.
  100. ##
  101. ## The ``data`` is application-defined data, which will be
  102. ## passed when signal raises.
  103. ##
  104. ## Returns the file descriptor for the registered signal.
  105. ##
  106. ## **Note:** This function is not supported on ``Windows``.
  107. proc registerProcess*[T](s: Selector[T], pid: int,
  108. data: T): int {.discardable.} =
  109. ## Registers a process id (pid) notification (when process has
  110. ## exited) in selector ``s``.
  111. ##
  112. ## The ``data`` is application-defined data, which will be passed when
  113. ## process with ``pid`` has exited.
  114. ##
  115. ## Returns the file descriptor for the registered signal.
  116. proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) =
  117. ## Registers selector event ``ev`` in selector ``s``.
  118. ##
  119. ## The ``data`` is application-defined data, which will be passed when
  120. ## ``ev`` happens.
  121. proc registerVnode*[T](s: Selector[T], fd: cint, events: set[Event],
  122. data: T) =
  123. ## Registers selector BSD/MacOSX specific vnode events for file
  124. ## descriptor ``fd`` and events ``events``.
  125. ## ``data`` application-defined data, which to be passed, when
  126. ## vnode event happens.
  127. ##
  128. ## **Note:** This function is supported only by BSD and MacOSX.
  129. proc newSelectEvent*(): SelectEvent =
  130. ## Creates a new user-defined event.
  131. proc trigger*(ev: SelectEvent) =
  132. ## Trigger event ``ev``.
  133. proc close*(ev: SelectEvent) =
  134. ## Closes user-defined event ``ev``.
  135. proc unregister*[T](s: Selector[T], ev: SelectEvent) =
  136. ## Unregisters user-defined event ``ev`` from selector ``s``.
  137. proc unregister*[T](s: Selector[T], fd: int|SocketHandle|cint) =
  138. ## Unregisters file/socket descriptor ``fd`` from selector ``s``.
  139. proc selectInto*[T](s: Selector[T], timeout: int,
  140. results: var openarray[ReadyKey]): int =
  141. ## Waits for events registered in selector ``s``.
  142. ##
  143. ## The ``timeout`` argument specifies the maximum number of milliseconds
  144. ## the function will be blocked for if no events are ready. Specifying a
  145. ## timeout of ``-1`` causes the function to block indefinitely.
  146. ## All available events will be stored in ``results`` array.
  147. ##
  148. ## Returns number of triggered events.
  149. proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey] =
  150. ## Waits for events registered in selector ``s``.
  151. ##
  152. ## The ``timeout`` argument specifies the maximum number of milliseconds
  153. ## the function will be blocked for if no events are ready. Specifying a
  154. ## timeout of ``-1`` causes the function to block indefinitely.
  155. ##
  156. ## Returns a list of triggered events.
  157. proc getData*[T](s: Selector[T], fd: SocketHandle|int): var T =
  158. ## Retrieves application-defined ``data`` associated with descriptor ``fd``.
  159. ## If specified descriptor ``fd`` is not registered, empty/default value
  160. ## will be returned.
  161. proc setData*[T](s: Selector[T], fd: SocketHandle|int, data: var T): bool =
  162. ## Associate application-defined ``data`` with descriptor ``fd``.
  163. ##
  164. ## Returns ``true``, if data was successfully updated, ``false`` otherwise.
  165. template isEmpty*[T](s: Selector[T]): bool = # TODO: Why is this a template?
  166. ## Returns ``true``, if there are no registered events or descriptors
  167. ## in selector.
  168. template withData*[T](s: Selector[T], fd: SocketHandle|int, value,
  169. body: untyped) =
  170. ## Retrieves the application-data assigned with descriptor ``fd``
  171. ## to ``value``. This ``value`` can be modified in the scope of
  172. ## the ``withData`` call.
  173. ##
  174. ## .. code-block:: nim
  175. ##
  176. ## s.withData(fd, value) do:
  177. ## # block is executed only if ``fd`` registered in selector ``s``
  178. ## value.uid = 1000
  179. ##
  180. template withData*[T](s: Selector[T], fd: SocketHandle|int, value,
  181. body1, body2: untyped) =
  182. ## Retrieves the application-data assigned with descriptor ``fd``
  183. ## to ``value``. This ``value`` can be modified in the scope of
  184. ## the ``withData`` call.
  185. ##
  186. ## .. code-block:: nim
  187. ##
  188. ## s.withData(fd, value) do:
  189. ## # block is executed only if ``fd`` registered in selector ``s``.
  190. ## value.uid = 1000
  191. ## do:
  192. ## # block is executed if ``fd`` not registered in selector ``s``.
  193. ## raise
  194. ##
  195. proc contains*[T](s: Selector[T], fd: SocketHandle|int): bool {.inline.} =
  196. ## Determines whether selector contains a file descriptor.
  197. proc getFd*[T](s: Selector[T]): int =
  198. ## Retrieves the underlying selector's file descriptor.
  199. ##
  200. ## For *poll* and *select* selectors ``-1`` is returned.
  201. else:
  202. import strutils
  203. when hasThreadSupport:
  204. import locks
  205. type
  206. SharedArray[T] = UncheckedArray[T]
  207. proc allocSharedArray[T](nsize: int): ptr SharedArray[T] =
  208. result = cast[ptr SharedArray[T]](allocShared0(sizeof(T) * nsize))
  209. proc reallocSharedArray[T](sa: ptr SharedArray[T], nsize: int): ptr SharedArray[T] =
  210. result = cast[ptr SharedArray[T]](reallocShared(sa, sizeof(T) * nsize))
  211. proc deallocSharedArray[T](sa: ptr SharedArray[T]) =
  212. deallocShared(cast[pointer](sa))
  213. type
  214. Event* {.pure.} = enum
  215. Read, Write, Timer, Signal, Process, Vnode, User, Error, Oneshot,
  216. Finished, VnodeWrite, VnodeDelete, VnodeExtend, VnodeAttrib, VnodeLink,
  217. VnodeRename, VnodeRevoke
  218. type
  219. IOSelectorsException* = object of CatchableError
  220. ReadyKey* = object
  221. fd*: int
  222. events*: set[Event]
  223. errorCode*: OSErrorCode
  224. SelectorKey[T] = object
  225. ident: int
  226. events: set[Event]
  227. param: int
  228. data: T
  229. const
  230. InvalidIdent = -1
  231. proc raiseIOSelectorsError[T](message: T) =
  232. var msg = ""
  233. when T is string:
  234. msg.add(message)
  235. elif T is OSErrorCode:
  236. msg.add(osErrorMsg(message) & " (code: " & $int(message) & ")")
  237. else:
  238. msg.add("Internal Error\n")
  239. var err = newException(IOSelectorsException, msg)
  240. raise err
  241. proc setNonBlocking(fd: cint) {.inline.} =
  242. setBlocking(fd.SocketHandle, false)
  243. when not defined(windows):
  244. import posix
  245. template setKey(s, pident, pevents, pparam, pdata: untyped) =
  246. var skey = addr(s.fds[pident])
  247. skey.ident = pident
  248. skey.events = pevents
  249. skey.param = pparam
  250. skey.data = pdata
  251. when ioselSupportedPlatform:
  252. template blockSignals(newmask: var Sigset, oldmask: var Sigset) =
  253. when hasThreadSupport:
  254. if posix.pthread_sigmask(SIG_BLOCK, newmask, oldmask) == -1:
  255. raiseIOSelectorsError(osLastError())
  256. else:
  257. if posix.sigprocmask(SIG_BLOCK, newmask, oldmask) == -1:
  258. raiseIOSelectorsError(osLastError())
  259. template unblockSignals(newmask: var Sigset, oldmask: var Sigset) =
  260. when hasThreadSupport:
  261. if posix.pthread_sigmask(SIG_UNBLOCK, newmask, oldmask) == -1:
  262. raiseIOSelectorsError(osLastError())
  263. else:
  264. if posix.sigprocmask(SIG_UNBLOCK, newmask, oldmask) == -1:
  265. raiseIOSelectorsError(osLastError())
  266. template clearKey[T](key: ptr SelectorKey[T]) =
  267. var empty: T
  268. key.ident = InvalidIdent
  269. key.events = {}
  270. key.data = empty
  271. proc verifySelectParams(timeout: int) =
  272. # Timeout of -1 means: wait forever
  273. # Anything higher is the time to wait in milliseconds.
  274. doAssert(timeout >= -1, "Cannot select with a negative value, got " & $timeout)
  275. when defined(linux) and not defined(emscripten):
  276. include ioselects/ioselectors_epoll
  277. elif bsdPlatform:
  278. include ioselects/ioselectors_kqueue
  279. elif defined(windows):
  280. include ioselects/ioselectors_select
  281. elif defined(solaris):
  282. include ioselects/ioselectors_poll # need to replace it with event ports
  283. elif defined(genode):
  284. include ioselects/ioselectors_select # TODO: use the native VFS layer
  285. elif defined(nintendoswitch):
  286. include ioselects/ioselectors_select
  287. elif defined(freertos) or defined(lwip):
  288. include ioselects/ioselectors_select
  289. else:
  290. include ioselects/ioselectors_poll