diff --git a/src/bin_node/node_run_command.ml b/src/bin_node/node_run_command.ml index 111fb9cda8ba6a75b76de4a2bfe437dffa39a2f3..988d863b25579e679e84431682b9ec1deb9f14b4 100644 --- a/src/bin_node/node_run_command.ml +++ b/src/bin_node/node_run_command.ml @@ -513,7 +513,7 @@ let launch_rpc_server ?middleware (config : Config_file.t) dir rpc_server_kind - No_server: the node is not responding to any RPC. *) type rpc_server_kind = | Local_rpc_server of RPC_server.server list - | External_rpc_server of (RPC_server.server * Rpc_process_worker.t) list + | External_rpc_server of (RPC_server.server * Rpc_process_worker.process) list | No_server (* Initializes an RPC server handled by the node main process. *) diff --git a/src/lib_base/unix/lwt_process_watchdog.ml b/src/lib_base/unix/lwt_process_watchdog.ml new file mode 100644 index 0000000000000000000000000000000000000000..e7464e0374e79d95cec48f15f0a2a8789ce46c26 --- /dev/null +++ b/src/lib_base/unix/lwt_process_watchdog.ml @@ -0,0 +1,312 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023-2024 Nomadic Labs *) +(* *) +(*****************************************************************************) + +type error += Process_init_too_slow + +let () = + register_error_kind + `Permanent + ~id:"process_worker.Process_init_too_slow" + ~title:"Process init too slow" + ~description:"Process init too slow" + ~pp:(fun ppf () -> + Format.fprintf + ppf + "Process init timeout: too slow to start. This is certainly due to the \ + slow DAL initialization.") + Data_encoding.unit + (function Process_init_too_slow -> Some () | _ -> None) + (fun () -> Process_init_too_slow) + +module type NAME = sig + val base : string list + + val component : string list +end + +module type EVENTS = sig + open Internal_event.Simple + + val emit : 'a t -> 'a -> unit Lwt.t + + val shutting_down_process : unit t + + val process_started : int t + + val process_exited_abnormally : (int * Unix.process_status) t + + val cannot_start_process : string t + + val waiting_for_process_restart : float t +end + +module MakeEvent (N : NAME) : EVENTS = struct + let emit = Internal_event.Simple.emit + + let section = N.base + + let component_base_name = String.concat "_" N.component + + let component_name = String.concat " " N.component + + let shutting_down_process = + let open Internal_event.Simple in + declare_0 + ~section + ~name:(Format.sprintf "shutting_down_%s" component_base_name) + ~msg:(Format.sprintf "shutting down the %s" component_name) + ~level:Notice + () + + let process_started = + let open Internal_event.Simple in + declare_1 + ~section + ~name:(Format.sprintf "%s_started" component_base_name) + ~msg:(Format.sprintf "%s was started on pid {pid}" component_name) + ~level:Notice + ("pid", Data_encoding.int31) + + let process_exited_abnormally = + let open Unix in + let exit_status_encoding = + let open Data_encoding in + union + [ + case + (Tag 0) + ~title:"wexited" + int31 + (function WEXITED i -> Some i | _ -> None) + (fun i -> WEXITED i); + case + (Tag 1) + ~title:"wsignaled" + int31 + (function WSIGNALED i -> Some i | _ -> None) + (fun i -> WSIGNALED i); + case + (Tag 2) + ~title:"wstopped" + int31 + (function WSTOPPED i -> Some i | _ -> None) + (fun i -> WSTOPPED i); + ] + in + let open Internal_event.Simple in + declare_2 + ~section + ~level:Error + ~name:(Format.sprintf "%s_exited_status" component_base_name) + ~msg:(Format.sprintf "%s (pid {pid}) {status_msg}" component_name) + ("pid", Data_encoding.int31) + ~pp2:(fun fmt status -> + match status with + | WEXITED i -> + Format.fprintf fmt "terminated abnormally with exit code %i" i + | WSIGNALED i -> + Format.fprintf + fmt + "was killed by signal %s" + (Lwt_exit.signal_name i) + | WSTOPPED i -> + Format.fprintf + fmt + "was stopped by signal %s" + (Lwt_exit.signal_name i)) + ("status_msg", exit_status_encoding) + + let cannot_start_process = + let open Internal_event.Simple in + declare_1 + ~section + ~name:(Format.sprintf "cannot_start_%s" component_base_name) + ~level:Error + ~msg:(Format.sprintf "cannot start %s: {trace}" component_name) + ("trace", Data_encoding.string) + + let waiting_for_process_restart = + let open Internal_event.Simple in + declare_1 + ~section + ~name:(Format.sprintf "waiting_for_%s_restart" component_base_name) + ~level:Error + ~msg:(Format.sprintf "restarting %s in {sleep} seconds" component_name) + ("sleep", Data_encoding.float) +end + +(* State of the worker. *) +type 'a t = { + mutable server : Lwt_process.process_none option; + (* Promise that aims to be resolved as soon as the server is + shutting down. *) + stop : (int * Unix.process_status) Lwt.t; + (* Resolver that will wakeup the above stop promise. *) + stopper : (int * Unix.process_status) Lwt.u; + (* The parameters that will be passed to the process after the + handshake.*) + parameters : 'a; + (* The parameters encoding associated to the above field. *) + parameters_encoding : 'a Data_encoding.t; +} + +let create ~parameters ~parameters_encoding = + let stop, stopper = Lwt.wait () in + {server = None; stop; stopper; parameters; parameters_encoding} + +(** [get_init_socket_path ~socket_dir ?socket_prefix ~pid ()] + generates the socket path in which the socket will be created. The + socket will be named from the [?socket_prefix] (a random filename + is generated if none) and the [pid] and will be located in + [socket_dir]. *) +let get_init_socket_path ~socket_dir ?socket_prefix ~pid () = + let socket_prefix = + match socket_prefix with + | Some v -> v + | None -> Filename.(temp_file ~temp_dir:"" "" "") + in + let filename = Format.sprintf "%s-%d" socket_prefix pid in + Filename.concat socket_dir filename + +module Daemon (Event : EVENTS) = struct + let shutdown t = + let open Lwt_syntax in + match t.server with + | None -> return_unit + | Some process -> + let* () = Event.(emit shutting_down_process) () in + process#terminate ; + return_unit + + let stop t = + Lwt.wakeup t.stopper (0, Lwt_unix.WSTOPPED 0) ; + shutdown t + + let run_process_with_sockets t ~process_name ?socket_prefix ?executable_name + ~handshake () = + let open Lwt_result_syntax in + let socket_dir = Socket.get_temporary_socket_dir () in + let socket_dir_arg = ["--socket-dir"; socket_dir] in + let args = process_name :: socket_dir_arg in + let executable_name = + Option.value executable_name ~default:Sys.executable_name + in + let process = + Lwt_process.open_process_none + ~stdout:(`FD_copy Unix.stdout) + ~stderr:(`FD_copy Unix.stderr) + (executable_name, Array.of_list args) + in + let pid = process#pid in + let init_socket_path = + get_init_socket_path ~socket_dir ?socket_prefix ~pid () + in + let* init_socket_fd = + let* fds = Socket.bind (Unix init_socket_path) in + match fds with + | [fd] -> + let*! init_socket_fd, _ = Lwt_unix.accept ~cloexec:true fd in + let*! () = Lwt_unix.close fd in + return init_socket_fd + | _ -> + (* This assertions holds as long as + Tezos_base_unix.Socket.bind returns a single list element + when binding Unix sockets. *) + assert false + in + let* () = Socket.handshake init_socket_fd handshake in + let* () = Socket.send init_socket_fd t.parameters_encoding t.parameters in + (* FIXME: https://gitlab.com/tezos/tezos/-/issues/6579 + Workaround: increase default timeout. If the timeout is still not + enough and an Lwt_unix.Timeout is triggered, we display a + comprehensive message. + *) + let timeout = Ptime.Span.of_int_s 120 in + let* () = + protect + (fun () -> Socket.recv ~timeout init_socket_fd Data_encoding.unit) + ~on_error:(function + | err + when List.exists + (function Exn Lwt_unix.Timeout -> true | _ -> false) + err -> + tzfail Process_init_too_slow + | e -> fail e) + in + let*! () = Lwt_unix.close init_socket_fd in + let*! () = Event.(emit process_started) pid in + t.server <- Some process ; + return t + + (* [run_with_backoff ~backoff f] evaluates [f]. If [f] fails, the + error is caught, printed as an error event, and [f] is re-evaluated + after a [backoff] delay. The delay increases at each failing + try. *) + let rec run_with_backoff ~backoff ~f = + let open Lwt_result_syntax in + let timestamp, sleep = backoff in + let now = Time.System.now () in + let diff = Ptime.diff now timestamp in + if Ptime.Span.to_float_s diff > sleep then + protect + (fun () -> f ()) + ~on_error:(function + | errs -> + let*! () = + Event.( + emit + cannot_start_process + (Format.asprintf "%a" pp_print_trace errs)) + in + run_with_backoff ~backoff:(Time.System.now (), sleep *. 1.2) ~f) + else + let*! () = Event.(emit waiting_for_process_restart sleep) in + let*! () = Lwt_unix.sleep sleep in + run_with_backoff ~backoff:(timestamp, sleep) ~f + + (* [watch_dog ~start_new_server] make sure that the RPC process is restarted as soon as it + dies. *) + let watch_dog ~start_new_server = + let open Lwt_result_syntax in + let initial_backoff = (Time.System.epoch, 0.5) in + let rec loop t = + match t.server with + | None -> + let* new_server = + run_with_backoff ~backoff:initial_backoff ~f:start_new_server + in + loop new_server + | Some process -> ( + let wait_pid_t = + let*! _, status = Lwt_unix.waitpid [] process#pid in + (* Sleep is necessary here to avoid waitpid to be faster than + the Lwt_exit stack. It avoids the clean_up_starts to be + pending while the node is properly shutting down. *) + let*! () = Lwt_unix.sleep 1. in + Lwt.return (`Wait_pid status) + in + let stop_t = + let*! _ = t.stop in + Lwt.return `Stopped + in + let*! res = Lwt.choose [wait_pid_t; stop_t] in + match res with + | `Stopped -> return_unit + | `Wait_pid _ when not (Lwt.is_sleeping Lwt_exit.clean_up_starts) -> + return_unit + | `Wait_pid status -> + t.server <- None ; + let*! () = + Event.(emit process_exited_abnormally) (process#pid, status) + in + let* new_server = + run_with_backoff ~backoff:initial_backoff ~f:start_new_server + in + loop new_server) + in + loop +end diff --git a/src/lib_base/unix/lwt_process_watchdog.mli b/src/lib_base/unix/lwt_process_watchdog.mli new file mode 100644 index 0000000000000000000000000000000000000000..f40a87c47ca634a671c098f87c0b16e97b91f93f --- /dev/null +++ b/src/lib_base/unix/lwt_process_watchdog.mli @@ -0,0 +1,76 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2023-2024 Nomadic Labs *) +(* *) +(*****************************************************************************) + +module type NAME = sig + val base : string list + + val component : string list +end + +module type EVENTS = sig + open Internal_event.Simple + + val emit : 'a t -> 'a -> unit Lwt.t + + val shutting_down_process : unit t + + val process_started : int t + + val process_exited_abnormally : (int * Unix.process_status) t + + val cannot_start_process : string t + + val waiting_for_process_restart : float t +end + +module MakeEvent : functor (N : NAME) -> EVENTS + +type 'a t + +(** [create ~parameters ~parameters_encoding] creates a watchdog + state, ready to be passed to the [Daemon] runner. *) +val create : parameters:'a -> parameters_encoding:'a encoding -> 'a t + +val get_init_socket_path : + socket_dir:string -> ?socket_prefix:string -> pid:int -> unit -> string + +module Daemon : functor (Event : EVENTS) -> sig + (** [stop t] stops the process handled by the watchdog daemon as + soon as it is called. *) + val stop : 'a t -> unit Lwt.t + + (** [run_process_with_sockets t ~process_name ?socket_prefix + ?executable_name ~handshake ()] starts a + [Lwt_process.process_none] depending on the given [process_name] + and [executable_name] parameters. If [executable_name] is + passed, then the process will be run thanks to the path to this + binary. Otherwise, the current binary name will be used as a + forked process. [process_name] aims to be the entry point of the + binary, that may differ from the [executable_name] in case of + fork. The [stdout] and [stderr] are redirected to the default + Unix streams. + + [socket_prefix] and [handshake] are used to setup the + communication, through a socket, with the created process. The + values ares expected to be defined accordingly to both parts. *) + val run_process_with_sockets : + 'a t -> + process_name:string -> + ?socket_prefix:string -> + ?executable_name:string -> + handshake:bytes -> + unit -> + 'a t tzresult Lwt.t + + (** [watch_dog ~start_new_server t] takes a running process [t] and + make sure it runs well. If it crashed, it will restart the + process using the given [start_new_server] callback. *) + val watch_dog : + start_new_server:(unit -> 'a t tzresult Lwt.t) -> + 'a t -> + (unit, tztrace) result Lwt.t +end diff --git a/src/lib_rpc_process/main.ml b/src/lib_rpc_process/main.ml index c835de639bc4df759526190ad7b5e020e2b919ae..78002b6fa568414690f8f09715f8c12f2180eafc 100644 --- a/src/lib_rpc_process/main.ml +++ b/src/lib_rpc_process/main.ml @@ -222,13 +222,6 @@ let init_rpc dynamic_store parameters head_watcher applied_blocks_watcher = in return_unit -let get_init_socket_path socket_dir pid = - let filename = Format.sprintf "init-rpc-socket-%d" pid in - Filename.concat socket_dir filename - -(* Magic bytes used for the external RPC process handshake. *) -let socket_magic = Bytes.of_string "TEZOS_RPC_SERVER_MAGIC_0" - let create_init_socket socket_dir = let open Lwt_result_syntax in let* socket_dir = @@ -237,11 +230,19 @@ let create_init_socket socket_dir = | None -> tzfail Missing_socket_dir in let pid = Unix.getpid () in - let init_socket_path = get_init_socket_path socket_dir pid in + let init_socket_path = + Lwt_process_watchdog.get_init_socket_path + ~socket_dir + ~socket_prefix:Rpc_process_worker.rpc_process_socket_prefix + ~pid + () + in let* init_socket_fd = Socket.connect (Unix init_socket_path) in (* Unlink the socket as soon as both sides have opened it.*) let*! () = Lwt_unix.unlink init_socket_path in - let* () = Socket.handshake init_socket_fd socket_magic in + let* () = + Socket.handshake init_socket_fd Rpc_process_worker.rpc_process_socket_magic + in return init_socket_fd let run socket_dir = diff --git a/src/lib_rpc_process/rpc_process_worker.ml b/src/lib_rpc_process/rpc_process_worker.ml index 56a8c2b8de96aa73890780e7e80de343e12bc063..1f68ec7f96e4243270c0cfa31508f4fb97ed541b 100644 --- a/src/lib_rpc_process/rpc_process_worker.ml +++ b/src/lib_rpc_process/rpc_process_worker.ml @@ -5,265 +5,49 @@ (* *) (*****************************************************************************) -type error += RPC_process_init_too_slow +module Name : Lwt_process_watchdog.NAME = struct + let base = ["node"; "main"] -let () = - register_error_kind - `Permanent - ~id:"rpc_process_worker.RPC_process_init_too_slow" - ~title:"RPC process init too slow" - ~description:"RPC process init too slow" - ~pp:(fun ppf () -> - Format.fprintf - ppf - "RPC process init timeout: too slow to start. This is certainly due to \ - the slow DAL initialization.") - Data_encoding.unit - (function RPC_process_init_too_slow -> Some () | _ -> None) - (fun () -> RPC_process_init_too_slow) - -module Event = struct - include Internal_event.Simple - - let section = ["node"; "main"] - - let shutting_down_rpc_process = - declare_0 - ~section - ~name:"shutting_down_rpc_process" - ~msg:"shutting down the RPC process" - ~level:Notice - () - - let rpc_process_started = - declare_1 - ~section - ~name:"rpc_process_started" - ~msg:"RPC process was started on pid {pid}" - ~level:Notice - ("pid", Data_encoding.int31) - - let rpc_process_exited_abnormally = - let open Unix in - let exit_status_encoding = - let open Data_encoding in - union - [ - case - (Tag 0) - ~title:"wexited" - int31 - (function WEXITED i -> Some i | _ -> None) - (fun i -> WEXITED i); - case - (Tag 1) - ~title:"wsignaled" - int31 - (function WSIGNALED i -> Some i | _ -> None) - (fun i -> WSIGNALED i); - case - (Tag 2) - ~title:"wstopped" - int31 - (function WSTOPPED i -> Some i | _ -> None) - (fun i -> WSTOPPED i); - ] - in - declare_2 - ~section - ~level:Error - ~name:"rpc_process_exited_status" - ~msg:"rpc process (pid {pid}) {status_msg}" - ("pid", Data_encoding.int31) - ~pp2:(fun fmt status -> - match status with - | WEXITED i -> - Format.fprintf fmt "terminated abnormally with exit code %i" i - | WSIGNALED i -> - Format.fprintf - fmt - "was killed by signal %s" - (Lwt_exit.signal_name i) - | WSTOPPED i -> - Format.fprintf - fmt - "was stopped by signal %s" - (Lwt_exit.signal_name i)) - ("status_msg", exit_status_encoding) - - let cannot_start_rpc_process = - declare_1 - ~section - ~name:"cannot_start_rpc_process" - ~level:Error - ~msg:"cannot start rpc process: {trace}" - ("trace", Data_encoding.string) - - let waiting_for_rpc_process_restart = - declare_1 - ~section - ~name:"waiting_for_rpc_process_restart" - ~level:Error - ~msg:"restarting RPC process in {sleep} seconds" - ("sleep", Data_encoding.float) + let component = ["rpc"; "process"] end -(* State of the worker. *) -type t = { - mutable server : Lwt_process.process_none option; - stop : (int * Unix.process_status) Lwt.t; - stopper : (int * Unix.process_status) Lwt.u; - external_process_parameters : Parameters.t; -} +module Event : Lwt_process_watchdog.EVENTS = + Lwt_process_watchdog.MakeEvent (Name) + +type process = Parameters.t Lwt_process_watchdog.t let create ~comm_socket_path (config : Config_file.t) node_version events_config = - let stop, stopper = Lwt.wait () in - { - server = None; - stop; - stopper; - external_process_parameters = + let parameters = + Parameters. { internal_events = events_config; config; rpc_comm_socket_path = comm_socket_path; node_version; - }; - } + } + in + Lwt_process_watchdog.create + ~parameters + ~parameters_encoding:Parameters.parameters_encoding -let shutdown t = - let open Lwt_syntax in - match t.server with - | None -> return_unit - | Some process -> - let* () = Event.(emit shutting_down_rpc_process) () in - process#terminate ; - return_unit +let rpc_process_socket_magic = Bytes.of_string "TEZOS_RPC_SERVER_MAGIC_0" -let stop t = - Lwt.wakeup t.stopper (0, Lwt_unix.WSTOPPED 0) ; - shutdown t +let rpc_process_socket_prefix = "init-rpc-socket" -let run_server t () = - let open Lwt_result_syntax in - let socket_dir = Tezos_base_unix.Socket.get_temporary_socket_dir () in - let socket_dir_arg = ["--socket-dir"; socket_dir] in - let args = "octez-rpc-process" :: socket_dir_arg in - let process = - Lwt_process.open_process_none - ~stdout:(`FD_copy Unix.stdout) - ~stderr:(`FD_copy Unix.stderr) - (Sys.executable_name, Array.of_list args) - in - let pid = process#pid in - let init_socket_path = Main.get_init_socket_path socket_dir pid in - let* init_socket_fd = - let* fds = Tezos_base_unix.Socket.bind (Unix init_socket_path) in - match fds with - | [fd] -> - let*! init_socket_fd, _ = Lwt_unix.accept ~cloexec:true fd in - let*! () = Lwt_unix.close fd in - return init_socket_fd - | _ -> - (* This assertions holds as long as - Tezos_base_unix.Socket.bind returns a single list element - when binding Unix sockets. *) - assert false - in - let* () = Tezos_base_unix.Socket.handshake init_socket_fd Main.socket_magic in - let* () = - Socket.send - init_socket_fd - Parameters.parameters_encoding - t.external_process_parameters - in - (* FIXME: https://gitlab.com/tezos/tezos/-/issues/6579 - Workaround: increase default timeout. If the timeout is still not - enough and an Lwt_unix.Timeout is triggered, we display a - comprehensive message. - *) - let timeout = Ptime.Span.of_int_s 120 in - let* () = - protect - (fun () -> Socket.recv ~timeout init_socket_fd Data_encoding.unit) - ~on_error:(function - | err - when List.exists - (function Exn Lwt_unix.Timeout -> true | _ -> false) - err -> - tzfail RPC_process_init_too_slow - | e -> fail e) - in - let*! () = Lwt_unix.close init_socket_fd in - let*! () = Event.(emit rpc_process_started) pid in - t.server <- Some process ; - return t +module Watchdog = Lwt_process_watchdog.Daemon (Event) -(* Evaluates [f]. If [f] fails, the error is caught, printed as an - error event, and [f] is re-evaluated after a [backoff] delay. The - delay increases at each failing try. *) -let rec may_start backoff f = - let open Lwt_result_syntax in - let timestamp, sleep = backoff in - let now = Time.System.now () in - let diff = Ptime.diff now timestamp in - if Ptime.Span.to_float_s diff > sleep then - protect - (fun () -> f ()) - ~on_error:(function - | errs -> - let*! () = - Event.( - emit - cannot_start_rpc_process - (Format.asprintf "%a" pp_print_trace errs)) - in - may_start (Time.System.now (), sleep *. 1.2) f) - else - let*! () = Event.(emit waiting_for_rpc_process_restart sleep) in - let*! () = Lwt_unix.sleep sleep in - may_start (timestamp, sleep) f +let stop (t : process) = Watchdog.stop t -(* Watch_dog make sure that the RPC process is restarted as soon as it - dies. *) -let watch_dog run_server = +let start parameters = let open Lwt_result_syntax in - let rec loop t = - match t.server with - | None -> - let* new_server = may_start (Time.System.epoch, 0.5) run_server in - loop new_server - | Some process -> ( - let wait_pid_t = - let*! _, status = Lwt_unix.waitpid [] process#pid in - (* Sleep is necessary here to avoid waitpid to be faster than - the Lwt_exit stack. It avoids the clean_up_starts to be - pending while the node is properly shutting down. *) - let*! () = Lwt_unix.sleep 1. in - Lwt.return (`Wait_pid status) - in - let stop_t = - let*! _ = t.stop in - Lwt.return `Stopped - in - let*! res = Lwt.choose [wait_pid_t; stop_t] in - match res with - | `Stopped -> return_unit - | `Wait_pid _ when not (Lwt.is_sleeping Lwt_exit.clean_up_starts) -> - return_unit - | `Wait_pid status -> - t.server <- None ; - let*! () = - Event.(emit rpc_process_exited_abnormally) (process#pid, status) - in - let* new_server = may_start (Time.System.epoch, 0.5) run_server in - loop new_server) + let run_process = + Watchdog.run_process_with_sockets + parameters + ~socket_prefix:rpc_process_socket_prefix + ~process_name:"octez-rpc-process" + ~handshake:rpc_process_socket_magic in - loop - -let start server = - let open Lwt_result_syntax in - let* new_server = run_server server () in - let _ = watch_dog (run_server server) new_server in + let* new_server = run_process () in + let _ = Watchdog.watch_dog ~start_new_server:run_process new_server in return_unit diff --git a/src/lib_rpc_process/rpc_process_worker.mli b/src/lib_rpc_process/rpc_process_worker.mli index 6d4500a4bee6aa34d83ff472f7f575dbdad094f7..f66d2bb5f949c5a1a297b8ea2462dbd714974a79 100644 --- a/src/lib_rpc_process/rpc_process_worker.mli +++ b/src/lib_rpc_process/rpc_process_worker.mli @@ -30,7 +30,7 @@ responsible of dealing with the process' lifetime. *) (** Type of the RPC process worker*) -type t +type process (** [create ~comm_socket_path config node_version internal_event_config] creates the worker initial state. [comm_socket_path] is a socket path that will be @@ -44,7 +44,7 @@ val create : Config_file.t -> Tezos_version.Octez_node_version.t -> Internal_event_config.t -> - t + process (** Starts the external RPC process using fork+exec calls. It implements a watch dog that is responsible of restarting the @@ -53,7 +53,13 @@ val create : until the restart is successful. The promise is blocking until the RPC server is fully available to answer to RPCs. *) -val start : t -> unit tzresult Lwt.t +val start : process -> unit tzresult Lwt.t (** Stops gracefully the RPC process worker*) -val stop : t -> unit Lwt.t +val stop : process -> unit Lwt.t + +(** Magic bytes used for the external RPC process handshake. *) +val rpc_process_socket_magic : bytes + +(** Name of the shared socket prefix of the RPC process. *) +val rpc_process_socket_prefix : string