Skip to content

Commit bcdae0b

Browse files
Copilotenghitalo
andcommitted
Add Windows backend implementation for vlib/os/notify
Co-authored-by: enghitalo <63821277+enghitalo@users.noreply.github.com>
1 parent 84be29a commit bcdae0b

1 file changed

Lines changed: 272 additions & 0 deletions

File tree

vlib/os/notify/backend_windows.c.v

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
module notify
2+
3+
import time
4+
5+
#flag windows -lws2_32
6+
7+
// Windows API declarations for pipe/file readiness checking
8+
fn C._get_osfhandle(int) isize
9+
fn C.PeekNamedPipe(voidptr, voidptr, u32, &u32, &u32, &u32) int
10+
fn C.GetFileType(voidptr) u32
11+
fn C.WaitForMultipleObjects(u32, &voidptr, int, u32) u32
12+
fn C.CreateEventW(voidptr, int, int, voidptr) voidptr
13+
fn C.SetEvent(voidptr) int
14+
fn C.ResetEvent(voidptr) int
15+
fn C.CloseHandle(voidptr) int
16+
fn C.WSAGetLastError() int
17+
fn C.GetLastError() u32
18+
19+
// Windows file type constants
20+
const file_type_char = u32(0x0002)
21+
const file_type_pipe = u32(0x0003)
22+
23+
// Wait result constants
24+
const wait_object_0 = u32(0x00000000)
25+
const wait_timeout = u32(0x00000102)
26+
const wait_failed = u32(0xFFFFFFFF)
27+
const invalid_handle_value = voidptr(-1)
28+
29+
// IocpNotifier provides methods that implement FdNotifier
30+
// using polling on Windows
31+
struct IocpNotifier {
32+
mut:
33+
// Map of file descriptor to info
34+
fd_map map[int]FdInfo
35+
wakeup_event voidptr
36+
}
37+
38+
struct FdInfo {
39+
mut:
40+
handle voidptr
41+
events FdEventType
42+
conf_flags []FdConfigFlags
43+
last_ready bool
44+
oneshot_triggered bool
45+
}
46+
47+
// IocpEvent describes an event that occurred for a file descriptor
48+
struct IocpEvent {
49+
pub:
50+
fd int
51+
kind FdEventType
52+
}
53+
54+
// new creates a new IocpNotifier.
55+
// The FdNotifier interface is returned to allow OS specific
56+
// implementations without exposing the concrete type
57+
pub fn new() !FdNotifier {
58+
// Create a manual-reset event for waking up
59+
wakeup := C.CreateEventW(voidptr(0), 1, 0, voidptr(0))
60+
if wakeup == voidptr(0) {
61+
return error('Failed to create wakeup event: ${C.GetLastError()}')
62+
}
63+
64+
// Needed to circumvent V limitations
65+
x := &IocpNotifier{
66+
fd_map: map[int]FdInfo{}
67+
wakeup_event: wakeup
68+
}
69+
return x
70+
}
71+
72+
// add adds a file descriptor to the watch list
73+
fn (mut in_ IocpNotifier) add(fd int, events FdEventType, conf ...FdConfigFlags) ! {
74+
// Get the OS handle for the file descriptor
75+
handle := C._get_osfhandle(fd)
76+
if handle == -1 {
77+
return error('Invalid file descriptor')
78+
}
79+
80+
// Check if already registered
81+
if fd in in_.fd_map {
82+
return error('File descriptor already registered')
83+
}
84+
85+
// Store the mapping
86+
in_.fd_map[fd] = FdInfo{
87+
handle: voidptr(handle)
88+
events: events
89+
conf_flags: conf.clone()
90+
last_ready: false
91+
oneshot_triggered: false
92+
}
93+
}
94+
95+
// modify sets an existing entry in the watch list to the provided events and configuration
96+
fn (mut in_ IocpNotifier) modify(fd int, events FdEventType, conf ...FdConfigFlags) ! {
97+
if fd !in in_.fd_map {
98+
return error('File descriptor not found')
99+
}
100+
101+
mut info := in_.fd_map[fd] or { return error('File descriptor not found') }
102+
info.events = events
103+
info.conf_flags = conf.clone()
104+
info.oneshot_triggered = false
105+
in_.fd_map[fd] = info
106+
}
107+
108+
// remove removes a file descriptor from the watch list
109+
fn (mut in_ IocpNotifier) remove(fd int) ! {
110+
if fd !in in_.fd_map {
111+
return error('File descriptor not found')
112+
}
113+
114+
in_.fd_map.delete(fd)
115+
}
116+
117+
// wait waits to be notified of events on the watch list
118+
fn (mut in_ IocpNotifier) wait(timeout time.Duration) []FdEvent {
119+
mut result := []FdEvent{}
120+
121+
start_time := time.now()
122+
timeout_ns := timeout.nanoseconds()
123+
124+
for {
125+
// Check all registered file descriptors for readiness
126+
for fd, mut info in in_.fd_map {
127+
// Skip if oneshot and already triggered
128+
if has_flag(info.conf_flags, .one_shot) && info.oneshot_triggered {
129+
continue
130+
}
131+
132+
mut current_events := unsafe { FdEventType(0) }
133+
134+
// Check for read readiness
135+
if info.events.has(.read) {
136+
if is_readable(info.handle) {
137+
current_events.set(.read)
138+
}
139+
}
140+
141+
// Check for write readiness (pipes are usually always writable)
142+
if info.events.has(.write) {
143+
if is_writable(info.handle) {
144+
current_events.set(.write)
145+
}
146+
}
147+
148+
// Check for hangup
149+
if info.events.has(.hangup) || info.events.has(.peer_hangup) {
150+
if is_closed(info.handle) {
151+
current_events.set(.hangup)
152+
}
153+
}
154+
155+
// Handle edge-triggered mode
156+
has_edge := has_flag(info.conf_flags, .edge_trigger)
157+
if has_edge {
158+
// Only report if state changed from not-ready to ready
159+
is_ready := !current_events.is_empty()
160+
if is_ready && !info.last_ready {
161+
info.last_ready = true
162+
in_.fd_map[fd] = info
163+
} else if !is_ready {
164+
info.last_ready = false
165+
in_.fd_map[fd] = info
166+
continue
167+
} else {
168+
// Was already ready, don't report
169+
continue
170+
}
171+
}
172+
173+
// If events occurred, add to result
174+
if !current_events.is_empty() {
175+
result << &IocpEvent{
176+
fd: fd
177+
kind: current_events
178+
}
179+
180+
// Mark oneshot as triggered
181+
if has_flag(info.conf_flags, .one_shot) {
182+
info.oneshot_triggered = true
183+
in_.fd_map[fd] = info
184+
}
185+
}
186+
}
187+
188+
// If we have events, return them
189+
if result.len > 0 {
190+
return result
191+
}
192+
193+
// Check if timeout expired
194+
if timeout_ns == 0 {
195+
return result
196+
}
197+
198+
elapsed := time.now() - start_time
199+
if elapsed.nanoseconds() >= timeout_ns {
200+
return result
201+
}
202+
203+
// Sleep a bit before checking again
204+
time.sleep(1 * time.millisecond)
205+
}
206+
207+
return result
208+
}
209+
210+
// close closes the IocpNotifier
211+
fn (mut in_ IocpNotifier) close() ! {
212+
in_.fd_map.clear()
213+
214+
if in_.wakeup_event != voidptr(0) {
215+
C.CloseHandle(in_.wakeup_event)
216+
in_.wakeup_event = voidptr(0)
217+
}
218+
}
219+
220+
// Helper function to check if handle is readable
221+
fn is_readable(handle voidptr) bool {
222+
file_type := C.GetFileType(handle)
223+
224+
// For pipes, use PeekNamedPipe
225+
if file_type == file_type_pipe {
226+
mut bytes_avail := u32(0)
227+
result := C.PeekNamedPipe(handle, voidptr(0), 0, voidptr(0), &bytes_avail, voidptr(0))
228+
if result != 0 && bytes_avail > 0 {
229+
return true
230+
}
231+
}
232+
233+
return false
234+
}
235+
236+
// Helper function to check if handle is writable
237+
fn is_writable(handle voidptr) bool {
238+
file_type := C.GetFileType(handle)
239+
240+
// For pipes, they're usually writable unless full
241+
// For now, assume writable
242+
if file_type == file_type_pipe {
243+
return true
244+
}
245+
246+
return false
247+
}
248+
249+
// Helper function to check if handle is closed/disconnected
250+
fn is_closed(handle voidptr) bool {
251+
file_type := C.GetFileType(handle)
252+
253+
// For pipes, check if PeekNamedPipe fails
254+
if file_type == file_type_pipe {
255+
result := C.PeekNamedPipe(handle, voidptr(0), 0, voidptr(0), voidptr(0), voidptr(0))
256+
if result == 0 {
257+
return true
258+
}
259+
}
260+
261+
return false
262+
}
263+
264+
// Helper function to check if a flag is set
265+
fn has_flag(flags []FdConfigFlags, flag FdConfigFlags) bool {
266+
for f in flags {
267+
if f.has(flag) {
268+
return true
269+
}
270+
}
271+
return false
272+
}

0 commit comments

Comments
 (0)