Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds native polling mode support on Windows #6087

Closed
wants to merge 5 commits into from

Conversation

yams-yams
Copy link
Contributor

Adds support for polling mode on Windows using a new internal library winwatch, which takes inspiration for its structure from fsevents. winwatch uses the Windows ReadDirectoryChangesW API to watch for file changes and an I/O Completion Port to report notifications in a callback. dune_file_watcher now uses winwatch instead of fswatch on native Windows, and notifications received in dune_file_watcher are filtered for relevance before they are passed to scheduler.thread_safe_send_emit_events_job.

One additional change was made for compatibility on Windows:
The rpc server typically uses Unix.pipe in 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_trie used by for fsevents.

Signed-off-by: Uma Kothuri [email protected]

@nojb
Copy link
Collaborator

nojb commented Aug 18, 2022

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!

Copy link
Collaborator

@nojb nojb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a first pass over the C code; I will do another pass later over the Caml code.

| [], [], [] ->
(* Timeout *)
if peek_named_pipe t.r_interrupt_accept then (
print_endline "data available!";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug left-over, can be removed.

@@ -0,0 +1,207 @@
#include <stdio.h>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think stdio.h can be removed.

Suggested change
#include <stdio.h>

Comment on lines +8 to +10
#include <caml/threads.h>
#include <caml/unixsupport.h>
#include <caml/custom.h>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these three be removed?

Suggested change
#include <caml/threads.h>
#include <caml/unixsupport.h>
#include <caml/custom.h>

Comment on lines 103 to 110
if (event->NextEntryOffset)
{
*((char**)&event) += event->NextEntryOffset;
}
else
{
break;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be simplified a bit:

Suggested change
if (event->NextEntryOffset)
{
*((char**)&event) += event->NextEntryOffset;
}
else
{
break;
}
if (event->NextEntryOffset == 0) break;
*((char**)&event) += event->NextEntryOffset;


for (;;)
{
name_len = event->FileNameLength / sizeof(WCHAR);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move this to below the switch so that it is closer to where it is used.

winwatch_watch(t);
}

CAMLreturn(Val_unit);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 96 to 99
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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 67 to 69
if (ok == FALSE)
/* Ignore errors */
continue;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cosmetic:

Suggested change
if (ok == FALSE)
/* Ignore errors */
continue;
if (ok == FALSE) /* Ignore errors */
continue;

Comment on lines 20 to 21
(** return None if the file does not exit *)
val create : string -> f:(Event.t -> string -> unit) -> t option
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(** 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. *)

| [], [], [] ->
(* Timeout *)
if peek_named_pipe t.r_interrupt_accept then (
print_endline "data available!";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left-over debug code.

Suggested change
print_endline "data available!";

@nojb
Copy link
Collaborator

nojb commented Aug 23, 2022

The DCO check is failing because your git "Author" name (git config user.name) must match your Signed-Off-by line, ie it must be Uma Kothuri. To change it:

  1. git config user.name "Uma Kothuri";
  2. git commit --reset-author --amend (to update the existing commit at the top of the branch with the new name).

@@ -575,6 +587,88 @@ let create_fsevents ?(latency = 0.2) ~(scheduler : Scheduler.t) () =
; sync_table
}

let winwatch_patterns =
List.rev_map ~f:Re.Str.regexp_case_fold
[ {|^_opam|}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of this list is already present in exclude_patterns in this file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are exactly the semantics of run? It looks like we could make it return _, or some kind of empty type.

@@ -590,11 +684,12 @@ let create_default ?fsevents_debounce ~scheduler () =
~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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's group this debounce_interval constant with the fswatch one in a module global.

break;
}

file_name = malloc(sizeof(WCHAR) * (name_len + 1));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

file_name is not freed (it can be right after it's been copied in v_file_name)

Comment on lines 96 to 99
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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this necessary? this is in t which is returned through v_res.

Signed-off-by: Uma Kothuri <[email protected]>
Signed-off-by: Nicolás Ojeda Bär <[email protected]>
nojb added 4 commits October 10, 2022 00:20
Signed-off-by: Nicolás Ojeda Bär <[email protected]>
Signed-off-by: Nicolás Ojeda Bär <[email protected]>
_
Signed-off-by: Nicolás Ojeda Bär <[email protected]>
_
Signed-off-by: Nicolás Ojeda Bär <[email protected]>
@rgrinberg
Copy link
Member

@nojb could you move the RPC changes to a separate PR?

@@ -0,0 +1,25 @@
module Iocp : sig
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add some docs to this module?

@@ -583,6 +595,88 @@ let create_fsevents ?(latency = 0.2) ~(scheduler : Scheduler.t) () =
; sync_table
}

let winwatch_patterns =
List.rev_map ~f:Re.Str.regexp_case_fold
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you switch from Re.Str to Re combinators?

@nojb
Copy link
Collaborator

nojb commented Oct 12, 2022

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.

@nojb
Copy link
Collaborator

nojb commented Feb 6, 2023

Superceded by #7010

@nojb nojb closed this Feb 6, 2023
@nojb nojb deleted the winwatch branch February 6, 2023 07:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants