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