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