Skip to content

Commit 3004a76

Browse files
committed
fix(fsevents): deduplicate fsevents watches
Since all watches are recursive, we need a special trie to do this easily Signed-off-by: Rudi Grinberg <[email protected]>
1 parent 348317e commit 3004a76

File tree

1 file changed

+91
-14
lines changed

1 file changed

+91
-14
lines changed

src/dune_file_watcher/dune_file_watcher.ml

Lines changed: 91 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,87 @@ module Scheduler = struct
6565
}
6666
end
6767

68+
module Watch_trie : sig
69+
(** Specialized trie for fsevent watches *)
70+
type 'a t
71+
72+
val empty : 'a t
73+
74+
val to_list : 'a t -> (Path.External.t * 'a) list
75+
76+
type 'a add =
77+
| Under_existing_node
78+
| Inserted of
79+
{ new_t : 'a t
80+
; removed : (Path.External.t * 'a) list
81+
}
82+
83+
val add : 'a t -> Path.External.t -> 'a Lazy.t -> 'a add
84+
end = struct
85+
(* the invariant is that a node can contain either a value or branches, but
86+
not both *)
87+
type 'a t =
88+
| Leaf of Path.External.t * 'a
89+
| Branch of 'a t String.Map.t
90+
91+
type 'a add =
92+
| Under_existing_node
93+
| Inserted of
94+
{ new_t : 'a t
95+
; removed : (Path.External.t * 'a) list
96+
}
97+
98+
let empty = Branch String.Map.empty
99+
100+
let to_list t =
101+
let rec loop t acc =
102+
match t with
103+
| Leaf (k, v) -> (k, v) :: acc
104+
| Branch m -> String.Map.fold m ~init:acc ~f:loop
105+
in
106+
loop t []
107+
108+
let rec path p a = function
109+
| [] -> Leaf (p, a)
110+
| x :: xs -> Branch (String.Map.singleton x (path p a xs))
111+
112+
let add t key v =
113+
(* wrong in general, but this is only needed for fsevents *)
114+
let comps =
115+
match String.split ~on:'/' (Path.External.to_string key) with
116+
| "" :: comps -> comps
117+
| _ ->
118+
(* fsevents gives us only absolute paths *)
119+
assert false
120+
in
121+
let rec add comps t =
122+
match (comps, t) with
123+
| _, Leaf (_, _) -> Under_existing_node
124+
| [], Branch _ ->
125+
Inserted { new_t = Leaf (key, Lazy.force v); removed = to_list t }
126+
| x :: xs, Branch m -> (
127+
match String.Map.find m x with
128+
| None ->
129+
Inserted
130+
{ new_t = Branch (String.Map.set m x (path key (Lazy.force v) xs))
131+
; removed = []
132+
}
133+
| Some m' -> (
134+
match add xs m' with
135+
| Under_existing_node -> Under_existing_node
136+
| Inserted i ->
137+
Inserted { i with new_t = Branch (String.Map.set m x i.new_t) }))
138+
in
139+
add comps t
140+
end
141+
68142
type kind =
69143
| Fswatch of
70144
{ pid : Pid.t
71145
; wait_for_watches_established : unit -> unit
72146
}
73147
| Fsevents of
74-
{ mutable external_ : Fsevents.t Path.External.Map.t
148+
{ mutable external_ : Fsevents.t Watch_trie.t
75149
; runloop : Fsevents.RunLoop.t
76150
; scheduler : Scheduler.t
77151
; source : Fsevents.t
@@ -179,7 +253,8 @@ let shutdown t =
179253
(fun () ->
180254
Fsevents.stop fsevents.source;
181255
Fsevents.stop fsevents.sync;
182-
Path.External.Map.iter fsevents.external_ ~f:Fsevents.stop;
256+
Watch_trie.to_list fsevents.external_
257+
|> List.iter ~f:(fun (_, fs) -> Fsevents.stop fs);
183258
Fsevents.RunLoop.stop fsevents.runloop)
184259

185260
let buffer_capacity = 65536
@@ -522,7 +597,7 @@ let create_fsevents ~(scheduler : Scheduler.t) =
522597
| Ok () -> ()
523598
| Error exn ->
524599
Code_error.raise "fsevents callback raised" [ ("exn", Exn.to_dyn exn) ]);
525-
let external_ = Path.External.Map.empty in
600+
let external_ = Watch_trie.empty in
526601
let runloop =
527602
Mutex.lock mutex;
528603
while !runloop_ref = None do
@@ -567,17 +642,19 @@ let add_watch t path =
567642
| Path.In_source_tree _ -> (* already watched by source watcher *) ()
568643
| In_build_dir _ ->
569644
Code_error.raise "attempted to watch a directory in build" []
570-
| External ext ->
571-
f.external_ <-
572-
Path.External.Map.update f.external_ ext ~f:(function
573-
| Some _ as s -> s
574-
| None ->
575-
let watch =
576-
fsevents f.scheduler ~paths:[ path ]
577-
(fsevents_standard_event ~ignored_files:t.ignored_files)
578-
in
579-
Fsevents.start watch f.runloop;
580-
Some watch));
645+
| External ext -> (
646+
let watch =
647+
lazy
648+
(fsevents f.scheduler ~paths:[ path ]
649+
(fsevents_standard_event ~ignored_files:t.ignored_files))
650+
in
651+
match Watch_trie.add f.external_ ext watch with
652+
| Watch_trie.Under_existing_node -> ()
653+
| Inserted { new_t; removed } ->
654+
let watch = Lazy.force watch in
655+
Fsevents.start watch f.runloop;
656+
List.iter removed ~f:(fun (_, fs) -> Fsevents.stop fs);
657+
f.external_ <- new_t));
581658
Ok ()
582659
| Fswatch _ ->
583660
(* Here we assume that the path is already being watched because the coarse

0 commit comments

Comments
 (0)