selectors.nim 13 KB

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