Skip to content

Commit 150d54a

Browse files
committed
fix: ref count merlin processes
Signed-off-by: Rudi Grinberg <[email protected]>
1 parent 163322c commit 150d54a

File tree

2 files changed

+81
-31
lines changed

2 files changed

+81
-31
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# 1.11.5
22

3+
- Fix process termination. Once the lsp server is stepped, the process will
4+
gracefully terminate (#697, fixes #694)
5+
36
- Forward stderr from dune's merlin configuration to the lsp server's stderr
47
(#697)
58

ocaml-lsp-server/src/merlin_config.ml

Lines changed: 78 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,15 @@ module Process = struct
153153
; session : Lev_fiber_csexp.Session.t
154154
}
155155

156+
let waitpid t =
157+
let+ status = Lev_fiber.waitpid ~pid:(Pid.to_int t.pid) in
158+
(match status with
159+
| Unix.WEXITED n when n <> 0 ->
160+
Format.eprintf "dune finished with code = %d@.%!" n
161+
| _ -> ());
162+
Lev_fiber.Io.close t.stdin;
163+
Lev_fiber.Io.close t.stdout
164+
156165
let start ~dir =
157166
match Bin.which "dune" with
158167
| None ->
@@ -190,50 +199,64 @@ module Process = struct
190199
{ pid; initial_cwd; stdin; stdout; session }
191200
end
192201

202+
module Dot_protocol_io =
203+
Merlin_dot_protocol.Make
204+
(Fiber)
205+
(struct
206+
include Lev_fiber_csexp.Session
207+
208+
let write t x = write t (Some [ x ])
209+
end)
210+
193211
type db =
194-
{ running : (string, Process.t) Table.t
212+
{ running : (string, entry) Table.t
195213
; pool : Fiber.Pool.t
196214
}
197215

216+
and entry =
217+
{ db : db
218+
; process : Process.t
219+
; mutable ref_count : int
220+
}
221+
222+
module Entry = struct
223+
type t = entry
224+
225+
let create db process = { db; process; ref_count = 0 }
226+
227+
let equal = ( == )
228+
229+
let incr t = t.ref_count <- t.ref_count + 1
230+
231+
let destroy (t : t) =
232+
assert (t.ref_count > 0);
233+
t.ref_count <- t.ref_count - 1;
234+
if t.ref_count > 0 then Fiber.return ()
235+
else (
236+
Table.remove t.db.running t.process.initial_cwd;
237+
Dot_protocol_io.Commands.halt t.process.session)
238+
end
239+
198240
let get_process t ~dir =
199241
match Table.find t.running dir with
200242
| Some p -> Fiber.return p
201243
| None ->
202-
let* p = Process.start ~dir in
203-
Table.add_exn t.running dir p;
204-
let+ () =
205-
Fiber.Pool.task t.pool ~f:(fun () ->
206-
let+ status = Lev_fiber.waitpid ~pid:(Pid.to_int p.pid) in
207-
(match status with
208-
| Unix.WEXITED n when n <> 0 ->
209-
Format.eprintf "dune finished with code = %d@.%!" n
210-
| _ -> ());
211-
Lev_fiber.Io.close p.stdin;
212-
Lev_fiber.Io.close p.stdout;
213-
Table.remove t.running dir)
214-
in
215-
p
244+
let* process = Process.start ~dir in
245+
let entry = Entry.create t process in
246+
Table.add_exn t.running dir entry;
247+
let+ () = Fiber.Pool.task t.pool ~f:(fun () -> Process.waitpid process) in
248+
entry
216249

217250
type context =
218251
{ workdir : string
219252
; process_dir : string
220253
}
221254

222-
module Dot_protocol_io =
223-
Merlin_dot_protocol.Make
224-
(Fiber)
225-
(struct
226-
include Lev_fiber_csexp.Session
227-
228-
let write t x = write t (Some [ x ])
229-
end)
230-
231-
let get_config db { workdir; process_dir } path_abs =
255+
let get_config (p : Process.t) ~workdir path_abs =
232256
let query path (p : Process.t) =
233257
let* () = Dot_protocol_io.Commands.send_file p.session path in
234258
Dot_protocol_io.read p.session
235259
in
236-
let* p = get_process db ~dir:process_dir in
237260
(* Both [p.initial_cwd] and [path_abs] have gone through
238261
[canonicalize_filename] *)
239262
let path_rel =
@@ -315,10 +338,16 @@ type nonrec t =
315338
{ path : string
316339
; directory : string
317340
; initial : Mconfig.t
341+
; mutable entry : Entry.t option
318342
; db : db
319343
}
320344

321-
let destroy _ = Fiber.return ()
345+
let destroy t =
346+
match t.entry with
347+
| None -> Fiber.return ()
348+
| Some entry ->
349+
t.entry <- None;
350+
Entry.destroy entry
322351

323352
let create db path =
324353
let path =
@@ -334,14 +363,32 @@ let create db path =
334363
; query = { init.query with filename; directory }
335364
}
336365
in
337-
{ path; directory; initial; db }
366+
{ path; directory; initial; db; entry = None }
338367

339368
let config (t : t) : Mconfig.t Fiber.t =
369+
let use_entry entry =
370+
Entry.incr entry;
371+
t.entry <- Some entry
372+
in
340373
let* () = Fiber.return () in
341374
match find_project_context t.directory with
342-
| None -> Fiber.return t.initial
343-
| Some (ctxt, config_path) ->
344-
let+ dot, failures = get_config t.db ctxt t.path in
375+
| None ->
376+
let+ () = destroy t in
377+
t.initial
378+
| Some (ctx, config_path) ->
379+
let* entry = get_process t.db ~dir:ctx.process_dir in
380+
let* () =
381+
match t.entry with
382+
| None ->
383+
use_entry entry;
384+
Fiber.return ()
385+
| Some entry' ->
386+
if Entry.equal entry entry' then Fiber.return ()
387+
else
388+
let+ () = destroy t in
389+
use_entry entry
390+
in
391+
let+ dot, failures = get_config entry.process ~workdir:ctx.workdir t.path in
345392
let merlin = Config.merge dot t.initial.merlin failures config_path in
346393
Mconfig.normalize { t.initial with merlin }
347394

0 commit comments

Comments
 (0)