selectors.nim 12 KB

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