selectors.nim 13 KB

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