| 1 | module notify |
| 2 | |
| 3 | import time |
| 4 | import os |
| 5 | |
| 6 | #include <sys/epoll.h> |
| 7 | |
| 8 | pub struct C.epoll_event { |
| 9 | events u32 |
| 10 | data C.epoll_data_t |
| 11 | } |
| 12 | |
| 13 | @[typedef] |
| 14 | union C.epoll_data_t { |
| 15 | ptr voidptr |
| 16 | fd int |
| 17 | u32 u32 |
| 18 | u64 u64 |
| 19 | } |
| 20 | |
| 21 | fn C.epoll_create1(i32) i32 |
| 22 | |
| 23 | fn C.epoll_ctl(i32, i32, i32, &C.epoll_event) i32 |
| 24 | |
| 25 | fn C.epoll_wait(i32, &C.epoll_event, i32, i32) i32 |
| 26 | |
| 27 | // EpollNotifier provides methods that implement FdNotifier using the |
| 28 | // epoll I/O event notification facility (linux only) |
| 29 | struct EpollNotifier { |
| 30 | epoll_fd int |
| 31 | } |
| 32 | |
| 33 | // EpollEvent describes an event that occurred for a file descriptor in |
| 34 | // the watch list |
| 35 | struct EpollEvent { |
| 36 | pub: |
| 37 | fd int |
| 38 | kind FdEventType |
| 39 | } |
| 40 | |
| 41 | // new creates a new EpollNotifier. |
| 42 | // The FdNotifier interface is returned to allow OS specific |
| 43 | // implementations without exposing the concrete type |
| 44 | pub fn new() !FdNotifier { |
| 45 | fd := C.epoll_create1(0) // 0 indicates default behavior |
| 46 | if fd == -1 { |
| 47 | return error(os.posix_get_error_msg(C.errno)) |
| 48 | } |
| 49 | // Needed to circumvent V limitations |
| 50 | x := &EpollNotifier{ |
| 51 | epoll_fd: fd |
| 52 | } |
| 53 | return x |
| 54 | } |
| 55 | |
| 56 | const epoll_read = u32(C.EPOLLIN) |
| 57 | const epoll_write = u32(C.EPOLLOUT) |
| 58 | const epoll_peer_hangup = u32(C.EPOLLRDHUP) |
| 59 | const epoll_exception = u32(C.EPOLLPRI) |
| 60 | const epoll_error = u32(C.EPOLLERR) |
| 61 | const epoll_hangup = u32(C.EPOLLHUP) |
| 62 | const epoll_edge_trigger = u32(C.EPOLLET) |
| 63 | const epoll_one_shot = u32(C.EPOLLONESHOT) |
| 64 | const epoll_wake_up = u32(C.EPOLLWAKEUP) |
| 65 | const epoll_exclusive = u32(C.EPOLLEXCLUSIVE) |
| 66 | |
| 67 | // ctl is a helper method for add, modify, and remove |
| 68 | fn (mut en EpollNotifier) ctl(fd int, op int, mask u32) ! { |
| 69 | event := C.epoll_event{ |
| 70 | events: mask |
| 71 | data: C.epoll_data_t{ |
| 72 | fd: fd |
| 73 | } |
| 74 | } |
| 75 | if C.epoll_ctl(en.epoll_fd, op, fd, &event) == -1 { |
| 76 | return error(os.posix_get_error_msg(C.errno)) |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | // add adds a file descriptor to the watch list |
| 81 | fn (mut en EpollNotifier) add(fd int, events FdEventType, conf ...FdConfigFlags) ! { |
| 82 | mask := flags_to_mask(events, ...conf) |
| 83 | en.ctl(fd, C.EPOLL_CTL_ADD, mask)! |
| 84 | } |
| 85 | |
| 86 | // modify sets an existing entry in the watch list to the provided events and configuration |
| 87 | fn (mut en EpollNotifier) modify(fd int, events FdEventType, conf ...FdConfigFlags) ! { |
| 88 | mask := flags_to_mask(events, ...conf) |
| 89 | en.ctl(fd, C.EPOLL_CTL_MOD, mask)! |
| 90 | } |
| 91 | |
| 92 | // remove removes a file descriptor from the watch list |
| 93 | fn (mut en EpollNotifier) remove(fd int) ! { |
| 94 | en.ctl(fd, C.EPOLL_CTL_DEL, 0)! |
| 95 | } |
| 96 | |
| 97 | // wait waits to be notified of events on the watch list, |
| 98 | // returns at most 512 events |
| 99 | fn (mut en EpollNotifier) wait(timeout time.Duration) []FdEvent { |
| 100 | // arbitrary 512 limit; events will round robin on successive |
| 101 | // waits if the number exceeds this |
| 102 | // NOTE: we use a fixed size array here for stack allocation; this has |
| 103 | // the added bonus of making EpollNotifier thread safe |
| 104 | events := [512]C.epoll_event{} |
| 105 | // populate events with the new events |
| 106 | to := timeout.sys_milliseconds() |
| 107 | count := C.epoll_wait(en.epoll_fd, &events[0], events.len, to) |
| 108 | |
| 109 | if count > 0 { |
| 110 | mut arr := []FdEvent{cap: count} |
| 111 | for i := 0; i < count; i++ { |
| 112 | fd := unsafe { events[i].data.fd } |
| 113 | kind := event_mask_to_flag(events[i].events) |
| 114 | if kind.is_empty() { |
| 115 | // NOTE: tcc only reports the first event for some |
| 116 | // reason, leaving subsequent structs in the array as 0 |
| 117 | // (or possibly garbage) |
| 118 | panic('encountered an empty event kind; this is most likely due to using tcc') |
| 119 | } |
| 120 | arr << &EpollEvent{ |
| 121 | fd: fd |
| 122 | kind: kind |
| 123 | } |
| 124 | } |
| 125 | return arr |
| 126 | } |
| 127 | return [] |
| 128 | } |
| 129 | |
| 130 | // close closes the EpollNotifier, |
| 131 | // any successive calls to add, modify, remove, and wait should fail |
| 132 | fn (mut en EpollNotifier) close() ! { |
| 133 | if C.close(en.epoll_fd) == -1 { |
| 134 | return error(os.posix_get_error_msg(C.errno)) |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | // event_mask_to_flag is a helper function that converts a bitmask |
| 139 | // returned by epoll_wait to FdEventType |
| 140 | fn event_mask_to_flag(mask u32) FdEventType { |
| 141 | mut flags := unsafe { FdEventType(0) } |
| 142 | |
| 143 | if mask & epoll_read != 0 { |
| 144 | flags.set(.read) |
| 145 | } |
| 146 | if mask & epoll_write != 0 { |
| 147 | flags.set(.write) |
| 148 | } |
| 149 | if mask & epoll_peer_hangup != 0 { |
| 150 | flags.set(.peer_hangup) |
| 151 | } |
| 152 | if mask & epoll_exception != 0 { |
| 153 | flags.set(.exception) |
| 154 | } |
| 155 | if mask & epoll_error != 0 { |
| 156 | flags.set(.error) |
| 157 | } |
| 158 | if mask & epoll_hangup != 0 { |
| 159 | flags.set(.hangup) |
| 160 | } |
| 161 | |
| 162 | return flags |
| 163 | } |
| 164 | |
| 165 | // flags_to_mask is a helper function that converts FdEventType and |
| 166 | // FdConfigFlags to a bitmask used by the C functions |
| 167 | fn flags_to_mask(events FdEventType, confs ...FdConfigFlags) u32 { |
| 168 | mut mask := u32(0) |
| 169 | if events.has(.read) { |
| 170 | mask |= epoll_read |
| 171 | } |
| 172 | if events.has(.write) { |
| 173 | mask |= epoll_write |
| 174 | } |
| 175 | if events.has(.peer_hangup) { |
| 176 | mask |= epoll_peer_hangup |
| 177 | } |
| 178 | if events.has(.exception) { |
| 179 | mask |= epoll_exception |
| 180 | } |
| 181 | if events.has(.error) { |
| 182 | mask |= epoll_error |
| 183 | } |
| 184 | if events.has(.hangup) { |
| 185 | mask |= epoll_hangup |
| 186 | } |
| 187 | for conf in confs { |
| 188 | if conf.has(.edge_trigger) { |
| 189 | mask |= epoll_edge_trigger |
| 190 | } |
| 191 | if conf.has(.one_shot) { |
| 192 | mask |= epoll_one_shot |
| 193 | } |
| 194 | if conf.has(.wake_up) { |
| 195 | mask |= epoll_wake_up |
| 196 | } |
| 197 | if conf.has(.exclusive) { |
| 198 | mask |= epoll_exclusive |
| 199 | } |
| 200 | } |
| 201 | return mask |
| 202 | } |
| 203 | |