Adds native polling mode support on Windows#6087
Adds native polling mode support on Windows#6087yams-yams wants to merge 5 commits intoocaml:mainfrom
Conversation
|
Just to add a little background information: @yams-yams worked on this project as part of an internship at LexiFi, and we have already discussed the general appraoch internally. I am planning to do a review of the PR, but of course additional reviewers are welcome! |
nojb
left a comment
There was a problem hiding this comment.
I did a first pass over the C code; I will do another pass later over the Caml code.
src/csexp_rpc/csexp_rpc.ml
Outdated
| | [], [], [] -> | ||
| (* Timeout *) | ||
| if peek_named_pipe t.r_interrupt_accept then ( | ||
| print_endline "data available!"; |
There was a problem hiding this comment.
Debug left-over, can be removed.
src/winwatch/winwatch_stubs.c
Outdated
| @@ -0,0 +1,207 @@ | |||
| #include <stdio.h> | |||
There was a problem hiding this comment.
I think stdio.h can be removed.
| #include <stdio.h> |
| #include <caml/threads.h> | ||
| #include <caml/unixsupport.h> | ||
| #include <caml/custom.h> |
There was a problem hiding this comment.
Can these three be removed?
| #include <caml/threads.h> | |
| #include <caml/unixsupport.h> | |
| #include <caml/custom.h> |
src/winwatch/winwatch_stubs.c
Outdated
| if (event->NextEntryOffset) | ||
| { | ||
| *((char**)&event) += event->NextEntryOffset; | ||
| } | ||
| else | ||
| { | ||
| break; | ||
| } |
There was a problem hiding this comment.
Can be simplified a bit:
| if (event->NextEntryOffset) | |
| { | |
| *((char**)&event) += event->NextEntryOffset; | |
| } | |
| else | |
| { | |
| break; | |
| } | |
| if (event->NextEntryOffset == 0) break; | |
| *((char**)&event) += event->NextEntryOffset; |
src/winwatch/winwatch_stubs.c
Outdated
|
|
||
| for (;;) | ||
| { | ||
| name_len = event->FileNameLength / sizeof(WCHAR); |
There was a problem hiding this comment.
I would move this to below the switch so that it is closer to where it is used.
| winwatch_watch(t); | ||
| } | ||
|
|
||
| CAMLreturn(Val_unit); |
There was a problem hiding this comment.
The function signature specifies that it returns (unit, exn) result, so this is wrong here. It should either return Ok () or Error exn (if the callback raises an exception). Currently it doesn't matter because we never exit the main loop.
src/winwatch/winwatch_stubs.c
Outdated
| file_name = malloc(sizeof(WCHAR) * (name_len + 1)); | ||
| memcpy(file_name, event->FileName, sizeof(WCHAR) * name_len); | ||
| file_name[name_len] = 0; | ||
| v_file_name = caml_copy_string_of_utf16(file_name); |
There was a problem hiding this comment.
Could use WideCharToMultiByte directly here to convert from UTF-16 to UTF-8 to avoid the extra copy.
https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-widechartomultibyte
There was a problem hiding this comment.
agree, you can allocate and copy directly into the ocaml value (if that's what you meant @nojb ?). no need to nul-terminate this way.
src/winwatch/winwatch_stubs.c
Outdated
| if (ok == FALSE) | ||
| /* Ignore errors */ | ||
| continue; |
There was a problem hiding this comment.
Cosmetic:
| if (ok == FALSE) | |
| /* Ignore errors */ | |
| continue; | |
| if (ok == FALSE) /* Ignore errors */ | |
| continue; |
src/winwatch/winwatch.mli
Outdated
| (** return None if the file does not exit *) | ||
| val create : string -> f:(Event.t -> string -> unit) -> t option |
There was a problem hiding this comment.
| (** return None if the file does not exit *) | |
| val create : string -> f:(Event.t -> string -> unit) -> t option | |
| val create : string -> f:(Event.t -> string -> unit) -> t option | |
| (** Returns [None] if the file does not exit. *) |
src/csexp_rpc/csexp_rpc.ml
Outdated
| | [], [], [] -> | ||
| (* Timeout *) | ||
| if peek_named_pipe t.r_interrupt_accept then ( | ||
| print_endline "data available!"; |
There was a problem hiding this comment.
Left-over debug code.
| print_endline "data available!"; |
|
The DCO check is failing because your git "Author" name (
|
|
|
||
| let winwatch_patterns = | ||
| List.rev_map ~f:Re.Str.regexp_case_fold | ||
| [ {|^_opam|} |
There was a problem hiding this comment.
Some of this list is already present in exclude_patterns in this file.
There was a problem hiding this comment.
Moreover, there's duplication within this list. I'd like to see this regex defined in a non redundant way.
| scheduler.spawn_thread (fun () -> | ||
| match Winwatch.Iocp.run iocp with | ||
| | Ok () -> () | ||
| | Error _ -> (* Winwatch.Iocp.run never returns *) assert false); |
There was a problem hiding this comment.
What are exactly the semantics of run? It looks like we could make it return _, or some kind of empty type.
| ~debounce_interval:(Some 0.5 (* seconds *)) ~backend | ||
| | `Fsevents -> create_fsevents ?latency:fsevents_debounce ~scheduler () | ||
| | `Inotify_lib -> create_inotifylib ~scheduler | ||
| | `Winwatch -> create_winwatch ~scheduler ~debounce_interval:0.5 |
There was a problem hiding this comment.
let's group this debounce_interval constant with the fswatch one in a module global.
src/winwatch/winwatch_stubs.c
Outdated
| break; | ||
| } | ||
|
|
||
| file_name = malloc(sizeof(WCHAR) * (name_len + 1)); |
There was a problem hiding this comment.
file_name is not freed (it can be right after it's been copied in v_file_name)
src/winwatch/winwatch_stubs.c
Outdated
| file_name = malloc(sizeof(WCHAR) * (name_len + 1)); | ||
| memcpy(file_name, event->FileName, sizeof(WCHAR) * name_len); | ||
| file_name[name_len] = 0; | ||
| v_file_name = caml_copy_string_of_utf16(file_name); |
There was a problem hiding this comment.
agree, you can allocate and copy directly into the ocaml value (if that's what you meant @nojb ?). no need to nul-terminate this way.
| t->overlapped = caml_stat_alloc(sizeof(OVERLAPPED)); | ||
| t->handle = handle; | ||
|
|
||
| caml_register_generational_global_root(&t->func); |
There was a problem hiding this comment.
is this necessary? this is in t which is returned through v_res.
Signed-off-by: Uma Kothuri <uma@kothuri.net> Signed-off-by: Nicolás Ojeda Bär <n.oje.bar@gmail.com>
Signed-off-by: Nicolás Ojeda Bär <n.oje.bar@gmail.com>
Signed-off-by: Nicolás Ojeda Bär <n.oje.bar@gmail.com>
|
@nojb could you move the RPC changes to a separate PR? |
| @@ -0,0 +1,25 @@ | |||
| module Iocp : sig | |||
There was a problem hiding this comment.
Could you add some docs to this module?
| } | ||
|
|
||
| let winwatch_patterns = | ||
| List.rev_map ~f:Re.Str.regexp_case_fold |
There was a problem hiding this comment.
Can you switch from Re.Str to Re combinators?
|
Thanks for the review @rgrinberg. I am in the process of rebasing it (I started a few days ago, but didn't get enough time to finish). Will amend according to your suggestions. |
|
Superceded by #7010 |
Adds support for polling mode on Windows using a new internal library
winwatch, which takes inspiration for its structure fromfsevents.winwatchuses the Windows ReadDirectoryChangesW API to watch for file changes and an I/O Completion Port to report notifications in a callback.dune_file_watchernow useswinwatchinstead offswatchon native Windows, and notifications received indune_file_watcherare filtered for relevance before they are passed toscheduler.thread_safe_send_emit_events_job.One additional change was made for compatibility on Windows:
The rpc server typically uses
Unix.pipein non-blocking mode, but non-blocking mode is not supported on Windows. The proposed solution instead uses a blocking pipe that times out every 0.1 second and reblocks.Potential areas for improvement:
The proposed solution does not prevent the same path from being watched multiple times. A potential solution would be a trie that stores paths, like
Watch_trieused by forfsevents.Signed-off-by: Uma Kothuri uma@kothuri.net