From 728f46a7a6bf29b1cf7fa5f15e78f8828b5e2fa6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr>
Date: Tue, 29 Mar 2022 11:25:19 +0200
Subject: [PATCH] [server] identifies cmdline stages

---
 src/plugins/server/main.ml  | 145 +++++++++++++++++++++++-------------
 src/plugins/server/main.mli |  33 ++++++--
 2 files changed, 121 insertions(+), 57 deletions(-)

diff --git a/src/plugins/server/main.ml b/src/plugins/server/main.ml
index 89c23c5871f..88512224c63 100644
--- a/src/plugins/server/main.ml
+++ b/src/plugins/server/main.ml
@@ -88,19 +88,24 @@ type 'a process = {
   mutable killed : bool ;
 }
 
+type 'a running =
+  | Idle (* Server is waiting for requests *)
+  | CmdLine (* Frama-C command line is running *)
+  | ExecRequest of 'a process (* Running EXEC process *)
+
 (* Server with request identifier (RqId) of type ['a] *)
 type 'a server = {
   pretty : Format.formatter -> 'a -> unit ; (* RqId printer *)
   equal : 'a -> 'a -> bool ; (* RqId equality *)
   polling : int ; (* server polling, in milliseconds *)
   fetch : unit -> 'a message option ; (* fetch some client message *)
-  q_in : 'a process Queue.t ; (* queue of pending jobs *)
+  q_in : 'a process Queue.t ; (* queue of pending `EXEC and `GET jobs *)
   q_out : 'a response Queue.t ; (* queue of pending responses *)
   mutable daemon : Db.daemon option ; (* Db.yield daemon *)
   mutable s_active : Signals.t ; (* signals the client is listening to *)
   mutable s_signal : Signals.t ; (* emitted signals since last synchro *)
   mutable shutdown : bool ; (* server has been asked to shut down *)
-  mutable running : 'a process option ; (* currently running EXEC request *)
+  mutable running : 'a running ; (* server running state *)
 }
 
 exception Killed
@@ -133,7 +138,15 @@ let pp_response pp fmt (r : _ response) =
   | `Killed id -> Format.fprintf fmt "Killed %a" pp id
   | `Signal sg -> Format.fprintf fmt "Signal %S" sg
   | `Data(id,data) ->
-    Format.fprintf fmt "@[<hov 2>Replies [%a]@ %a@]" pp id Data.pretty data
+    if Senv.debug_atleast 3 then
+      Format.fprintf fmt "@[<hov 2>Response %a:@ %a@]" pp id Data.pretty data
+    else
+      Format.fprintf fmt "Replied %a" pp id
+
+let pp_running pp fmt = function
+  | Idle -> Format.pp_print_string fmt "Idle"
+  | CmdLine -> Format.pp_print_string fmt "CmdLine"
+  | ExecRequest { id } -> Format.fprintf fmt "ExectRequest [%a]" pp id
 
 (* -------------------------------------------------------------------------- *)
 (* --- Request Handling                                                   --- *)
@@ -202,9 +215,21 @@ let emit s = !emitter s
 (* --- Processing Requests                                                --- *)
 (* -------------------------------------------------------------------------- *)
 
-let raise_if_killed = function { killed } -> if killed then raise Killed
-let kill_exec e = e.killed <- true
-let kill_request eq id e = if eq id e.id then e.killed <- true
+let raise_if_killed = function
+  | Idle -> ()
+  | CmdLine -> ()
+  | ExecRequest { killed } -> if killed then raise Killed
+
+let kill_running ?id s =
+  match s.running with
+  | Idle -> ()
+  | CmdLine -> if id = None then Db.cancel ()
+  | ExecRequest p ->
+    match id with
+    | None -> p.killed <- true
+    | Some id -> if s.equal id p.id then p.killed <- true
+
+let kill_request eq id p = if eq id p.id then p.killed <- true
 
 let process_request (server : 'a server) (request : 'a request) : unit =
   if Senv.debug_atleast 1 && (Senv.debug_atleast 3 || request <> `Poll) then
@@ -213,7 +238,7 @@ let process_request (server : 'a server) (request : 'a request) : unit =
   | `Poll -> ()
   | `Shutdown ->
     begin
-      Option.iter kill_exec server.running ;
+      kill_running server ;
       Queue.clear server.q_in ;
       Queue.clear server.q_out ;
       server.shutdown <- true ;
@@ -230,9 +255,9 @@ let process_request (server : 'a server) (request : 'a request) : unit =
     end
   | `Kill id ->
     begin
+      kill_running ~id server ;
       let set_killed = kill_request server.equal id in
       Queue.iter set_killed server.q_in ;
-      Option.iter set_killed server.running ;
     end
   | `Request(id,request,data) ->
     begin
@@ -278,7 +303,7 @@ let communicate server =
 (* -------------------------------------------------------------------------- *)
 
 let do_yield server () =
-  Option.iter raise_if_killed server.running ;
+  raise_if_killed server.running ;
   ignore ( communicate server )
 
 let do_signal server s =
@@ -301,14 +326,14 @@ let rec fetch_exec q =
 let process server =
   match fetch_exec server.q_in with
   | None -> communicate server
-  | Some proc ->
-    server.running <- Some proc ;
+  | Some exec ->
+    server.running <- ExecRequest exec ;
     try
-      execute server ~yield:(do_yield server) proc ;
-      server.running <- None ;
+      execute server ~yield:(do_yield server) exec ;
+      server.running <- Idle ;
       true
     with exn ->
-      server.running <- None ;
+      server.running <- Idle ;
       raise exn
 
 (* -------------------------------------------------------------------------- *)
@@ -333,7 +358,7 @@ let create ~pretty ?(equal=(=)) ~fetch () =
     s_active = Signals.empty ;
     s_signal = Signals.empty ;
     daemon = None ;
-    running = None ;
+    running = Idle ;
     shutdown = false ;
   }
 
@@ -341,49 +366,69 @@ let create ~pretty ?(equal=(=)) ~fetch () =
 (* --- Start / Stop                                                       --- *)
 (* -------------------------------------------------------------------------- *)
 
+(* public API ; shall be scheduled at command line main stage *)
 let start server =
-  emitter := do_signal server ;
-  match server.daemon with
-  | Some _ -> ()
-  | None ->
-    begin
-      Senv.feedback "Server enabled." ;
-      let daemon =
-        Db.on_progress
-          ~debounced:server.polling
-          ?on_delayed:(delayed "command line")
-          (do_yield server)
-      in
-      server.daemon <- Some daemon ;
-      set_active true ;
-    end
-
+  begin
+    Senv.debug ~level:2 "Server Start (was %a)"
+      (pp_running server.pretty) server.running ;
+    server.running <- CmdLine ;
+    emitter := do_signal server ;
+    match server.daemon with
+    | Some _ -> ()
+    | None ->
+      begin
+        Senv.feedback "Server enabled." ;
+        let daemon =
+          Db.on_progress
+            ~debounced:server.polling
+            ?on_delayed:(delayed "command line")
+            (do_yield server)
+        in
+        server.daemon <- Some daemon ;
+        set_active true ;
+      end
+  end
+
+(* public API ; can be invoked to force server shutdown *)
 let stop server =
-  emitter := nop ;
-  match server.daemon with
-  | None -> ()
-  | Some daemon ->
-    begin
-      Senv.feedback "Server disabled." ;
-      server.daemon <- None ;
-      Db.off_progress daemon ;
-      set_active false ;
-    end
-
+  begin
+    Senv.debug ~level:2 "Server Stop (was %a)"
+      (pp_running server.pretty) server.running ;
+    kill_running server ;
+    emitter := nop ;
+    match server.daemon with
+    | None -> ()
+    | Some daemon ->
+      begin
+        Senv.feedback "Server disabled." ;
+        server.daemon <- None ;
+        server.running <- Idle ;
+        Db.off_progress daemon ;
+        set_active false ;
+      end
+  end
+
+(* internal only *)
 let foreground server =
-  emitter := do_signal server ;
-  match server.daemon with
-  | None -> ()
-  | Some daemon ->
-    begin
-      server.daemon <- None ;
-      Db.off_progress daemon ;
-    end
+  begin
+    Senv.debug ~level:2 "Server Foreground (was %a)"
+      (pp_running server.pretty) server.running ;
+    server.running <- Idle ;
+    emitter := do_signal server ;
+    match server.daemon with
+    | None -> ()
+    | Some daemon ->
+      begin
+        server.daemon <- None ;
+        Db.off_progress daemon ;
+      end
+  end
 
 (* -------------------------------------------------------------------------- *)
 (* --- Main Loop                                                          --- *)
 (* -------------------------------------------------------------------------- *)
 
+(* public API ; shall be invoked at command line normal exit *)
 let run server =
   try
     ( (* TODO: catch-break to be removed once Why3 signal handler is fixed *)
diff --git a/src/plugins/server/main.mli b/src/plugins/server/main.mli
index 167692bbafd..9f90d6e6a14 100644
--- a/src/plugins/server/main.mli
+++ b/src/plugins/server/main.mli
@@ -89,19 +89,38 @@ val create :
   fetch:(unit -> 'a message option) ->
   unit -> 'a server
 
-(** Run the server forever.
-    The function will {i not} return until the server is actually shut down. *)
-val run : 'a server -> unit
-
 (** Start the server in background.
 
-    The function returns immediately after installing a daemon that accepts GET
-    requests received by the server on calls to [Db.yield()]. *)
+    The function returns immediately after installing a daemon that (only)
+    accepts GET requests received by the server on calls to [Db.yield()].
+
+    Shall be scheduled at command line main stage {i via}
+    [Db.Main.extend] extension point.
+*)
 val start : 'a server -> unit
 
-(** Stop the server if it is running in background. *)
+(** Stop the server if it is running in background.
+
+    Can be invoked to force server shutdown at any time.
+
+    It shall be typically scheduled {i via} [Extlib.safe_at_exit] along with
+    other system cleanup operations to make sure the server is property
+    shutdown before Frama-C main process exits.
+*)
 val stop : 'a server -> unit
 
+(** Run the server forever.
+    The server would now accept any kind of requests and start handling them.
+    While executing an [`EXEC] request, the server would
+    continue to handle (only) [`GET] pending requests on [Db.yield()]
+    at every [server.polling] time interval.
+
+    The function will {i not} return until the server is actually shutdown.
+
+    Shall be scheduled at normal command line termination {i via}
+    [Cmdline.at_normal_exit] extension point. *)
+val run : 'a server -> unit
+
 (** Kill the currently running request by raising an exception. *)
 val kill : unit -> 'a
 
-- 
GitLab