v2 / vlib / os / notify / backend_darwin.c.v
227 lines · 202 sloc · 6.13 KB · a87a4d73b9ab25cfff0822f4e94cf2a2d9e64323
Raw
1module notify
2
3import time
4import os
5
6#insert "@VEXEROOT/vlib/os/notify/kqueue.h"
7
8pub struct C.kevent {
9mut:
10 ident u32
11 filter i16
12 flags u16
13 fflags u32
14 data int
15 udata voidptr
16}
17
18fn C.kqueue() i32
19fn C.__kevent__(i32, voidptr, i32, voidptr, i32, voidptr) i32
20fn C.EV_SET(voidptr, u32, i16, u16, u32, i32, voidptr)
21
22// KqueueNotifier provides methods that implement FdNotifier using the
23// kqueue I/O event notification facility (macos, freeBSD, xxxBSD...unix only)
24struct KqueueNotifier {
25 kqueue_fd int
26}
27
28// KqueueEvent describes an event that occurred for a file descriptor in
29// the watch list
30struct KqueueEvent {
31pub:
32 fd int
33 kind FdEventType
34}
35
36// new creates a new KqueueNotifier.
37// The FdNotifier interface is returned to allow OS specific
38// implementations without exposing the concrete type
39pub fn new() !FdNotifier {
40 fd := C.kqueue()
41 if fd == -1 {
42 return error(os.posix_get_error_msg(C.errno))
43 }
44 // Needed to circumvent V limitations
45 x := &KqueueNotifier{
46 kqueue_fd: fd
47 }
48 return x
49}
50
51// filter types
52const kqueue_read = i16(C.EVFILT_READ)
53const kqueue_write = i16(C.EVFILT_WRITE)
54const kqueue_aio = i16(C.EVFILT_AIO)
55const kqueue_vnode = i16(C.EVFILT_VNODE)
56const kqueue_proc = i16(C.EVFILT_PROC)
57const kqueue_signal = i16(C.EVFILT_SIGNAL)
58const kqueue_timer = i16(C.EVFILT_TIMER)
59const kqueue_machport = i16(C.EVFILT_MACHPORT)
60const kqueue_fs = i16(C.EVFILT_FS)
61const kqueue_user = i16(C.EVFILT_USER)
62const kqueue_vm = i16(C.EVFILT_VM)
63const kqueue_exception = i16(C.EVFILT_EXCEPT)
64const kqueue_syscount = i16(C.EVFILT_SYSCOUNT)
65
66// actions
67const kqueue_add = u16(C.EV_ADD)
68const kqueue_delete = u16(C.EV_DELETE)
69const kqueue_enable = u16(C.EV_ENABLE)
70const kqueue_disable = u16(C.EV_DISABLE)
71
72// flags
73const kqueue_oneshot = u16(C.EV_ONESHOT)
74const kqueue_edge_trigger = u16(C.EV_CLEAR) // kqueue_clear
75
76const kqueue_receipt = u16(C.EV_RECEIPT)
77const kqueue_dispatch = u16(C.EV_DISPATCH)
78const kqueue_udata_specific = u16(C.EV_UDATA_SPECIFIC)
79const kqueue_dispatch2 = u16(C.EV_DISPATCH | C.EV_UDATA_SPECIFIC)
80const kqueue_vanished = u16(C.EV_VANISHED)
81const kqueue_sysflags = u16(C.EV_SYSFLAGS)
82const kqueue_flag0 = u16(C.EV_FLAG0)
83const kqueue_flag1 = u16(C.EV_FLAG1)
84
85// returned values
86const kqueue_eof = u16(C.EV_EOF)
87const kqueue_error = u16(C.EV_ERROR)
88
89// ctl is a helper method for add, modify, and remove
90fn (mut kn KqueueNotifier) ctl(fd int, filter i16, flags u16) ! {
91 event := [1]C.kevent{}
92 C.EV_SET(&event[0], fd, filter, flags, 0, 0, unsafe { nil })
93 if C.__kevent__(kn.kqueue_fd, &event[0], 1, unsafe { nil }, 0, unsafe { nil }) == -1 {
94 return error(os.posix_get_error_msg(C.errno))
95 }
96}
97
98// add adds a file descriptor to the watch list
99fn (mut kn KqueueNotifier) add(fd int, events FdEventType, conf ...FdConfigFlags) ! {
100 filter := filter_to_mask(events)
101 flags := flags_to_mask(...conf)
102 kn.ctl(fd, filter, flags)!
103}
104
105// modify sets an existing entry in the watch list to the provided events and configuration
106fn (mut kn KqueueNotifier) modify(fd int, events FdEventType, conf ...FdConfigFlags) ! {
107 kn.add(fd, events, ...conf)!
108}
109
110// remove removes a file descriptor from the watch list
111fn (mut kn KqueueNotifier) remove(fd int) ! {
112 filter := kqueue_read | kqueue_write | kqueue_exception
113 flags := kqueue_delete
114 kn.ctl(fd, filter, flags)!
115}
116
117// wait waits to be notified of events on the watch list,
118// returns at most 512 events
119fn (mut kn KqueueNotifier) wait(timeout time.Duration) []FdEvent {
120 // arbitrary 512 limit; events will round robin on successive
121 // waits if the number exceeds this
122 // NOTE: we use a fixed size array here for stack allocation; this has
123 // the added bonus of making KqueueNotifier thread safe
124 events := [512]C.kevent{}
125 // populate events with the new events
126 to := &C.timespec{0, timeout.nanoseconds()}
127 count := C.__kevent__(kn.kqueue_fd, unsafe { nil }, 0, &events[0], events.len, to)
128
129 if count > 0 {
130 mut arr := []FdEvent{cap: count}
131 for i := 0; i < count; i++ {
132 fd := int(events[i].ident)
133 kind := event_mask_to_flag(events[i].filter, events[i].flags)
134 if kind.is_empty() {
135 // NOTE: tcc only reports the first event for some
136 // reason, leaving subsequent structs in the array as 0
137 // (or possibly garbage)
138 panic('encountered an empty event kind; this is most likely due to using tcc')
139 }
140 arr << &KqueueEvent{
141 fd: fd
142 kind: kind
143 }
144 }
145 return arr
146 }
147 return []
148}
149
150// close closes the KqueueNotifier,
151// any successive calls to add, modify, remove, and wait should fail
152fn (mut kn KqueueNotifier) close() ! {
153 if C.close(kn.kqueue_fd) == -1 {
154 return error(os.posix_get_error_msg(C.errno))
155 }
156}
157
158// event_mask_to_flag is a helper function that converts a bitmask
159// returned by kevent() wait to FdEventType
160fn event_mask_to_flag(filter i16, flags u16) FdEventType {
161 mut res := unsafe { FdEventType(0) }
162
163 if filter & kqueue_read != 0 {
164 res.set(.read)
165 }
166 if filter & kqueue_write != 0 {
167 res.set(.write)
168 }
169 if filter & kqueue_exception != 0 {
170 res.set(.exception)
171 }
172
173 if flags & kqueue_eof != 0 {
174 res.set(.hangup)
175 }
176 if flags & kqueue_error != 0 {
177 res.set(.error)
178 }
179
180 return res
181}
182
183// filter_to_mask is a helper function that converts FdEventType
184// to a bitmask used by the C functions
185fn filter_to_mask(events FdEventType) i16 {
186 mut mask := i16(0)
187 if events.has(.read) {
188 mask |= kqueue_read
189 }
190 if events.has(.write) {
191 mask |= kqueue_write
192 }
193 if events.has(.exception) {
194 mask |= kqueue_exception
195 }
196 if events.has(.peer_hangup) {
197 panic("Kqueue does not support 'peer_hangup' event type.")
198 }
199 if events.has(.error) {
200 panic("Kqueue does not support 'error' event type.")
201 }
202 if events.has(.hangup) {
203 panic("Kqueue does not support 'hangup' event type.")
204 }
205 return mask
206}
207
208// flags_to_mask is a helper function that converts FdConfigFlags
209// to a bitmask used by the C functions
210fn flags_to_mask(confs ...FdConfigFlags) u16 {
211 mut mask := kqueue_add | kqueue_enable
212 for conf in confs {
213 if conf.has(.edge_trigger) {
214 mask |= kqueue_edge_trigger
215 }
216 if conf.has(.one_shot) {
217 mask |= kqueue_oneshot
218 }
219 if conf.has(.wake_up) {
220 panic("Kqueue does not support 'wake_up' flag.")
221 }
222 if conf.has(.exclusive) {
223 panic("Kqueue does not support 'exclusive' flag.")
224 }
225 }
226 return mask
227}
228