(* $Id: netplex_main.ml 1836 2013-02-10 20:58:39Z gerd $ *) open Netplex_types open Printf type cmdline_config = { mutable config_filename_opt : string option; mutable config_tree_opt : config_tree option; mutable pidfile : string option; mutable foreground : bool; } let is_win32 = match Sys.os_type with | "Win32" -> true | _ -> false;; let create ?config_filename ?config_tree ?(pidfile = None) ?(foreground = false) () = { config_filename_opt = config_filename; config_tree_opt = config_tree; pidfile = pidfile; foreground = foreground } let modify ?config_filename ?config_tree ?pidfile ?foreground cfg = { config_filename_opt = ( match config_filename with | Some f -> Some f | None -> cfg.config_filename_opt ); config_tree_opt = ( match config_tree with | Some f -> Some f | None -> cfg.config_tree_opt ); pidfile = ( match pidfile with | Some popt -> popt | None -> cfg.pidfile ); foreground = ( match foreground with | Some fg -> fg | None -> cfg.foreground ) } let args ?(defaults = create()) () = let config = (* copy of defaults: *) modify defaults in let spec = [ "-conf", (Arg.String (fun s -> config.config_filename_opt <- Some s)), "<file> Read this configuration file"; "-pid", (Arg.String (fun s -> config.pidfile <- Some s)), "<file> Write this PID file"; "-fg", (Arg.Unit (fun () -> config.foreground <- true)), " Start in the foreground and do not run as daemon"; ] in (spec, config) ;; let config_filename cf = match cf.config_filename_opt with | Some f -> f | None -> let command_name = Sys.argv.(0) in ( try (Filename.chop_extension command_name) ^ ".conf" with | _ -> command_name ^ ".conf" ) let config_filename_opt cf = cf.config_filename_opt let config_tree_opt cf = cf.config_tree_opt let pidfile cf = cf.pidfile let foreground cf = cf.foreground let daemon f = (* Double fork to avoid becoming a pg leader. The outer process waits until the most important initializations of the child are done (e.g. master sockets are created). *) if is_win32 then failwith "Startup as daemon is unsupported on Win32 - use -fg switch"; let oldmask = Unix.sigprocmask Unix.SIG_BLOCK [Sys.sigusr1; Sys.sigchld] in let pid = Unix.getpid() in match Unix.fork() with | 0 -> ( match Unix.fork() with | 0 -> ignore(Unix.sigprocmask Unix.SIG_SETMASK oldmask); let _ = Unix.setsid() in (* Start new session/get rid of tty *) (* Assign stdin/stdout to /dev/null *) Unix.close Unix.stdin; ignore(Unix.openfile "/dev/null" [ Unix.O_RDONLY ] 0); Unix.close Unix.stdout; ignore(Unix.openfile "/dev/null" [ Unix.O_WRONLY ] 0); (* Keep stderr open: error messages should appear *) Netsys_posix.run_post_fork_handlers(); f ~init_done:(fun () -> Unix.kill pid Sys.sigusr1) | _ -> Netsys._exit 0 ) | middle_pid -> (* Wait for zombie: *) ignore(Netsys.restart (Unix.waitpid []) middle_pid); (* Wait for SIGUSR1, but ignore SIGCHLD *) Sys.set_signal Sys.sigusr1 (Sys.Signal_handle (fun _ -> ())); Unix.sigsuspend [ Sys.sigchld ]; ignore(Unix.sigprocmask Unix.SIG_SETMASK oldmask); ;; let rec run_controller ctrl = try Unixqueue.run ctrl#event_system with | error -> ctrl # logger # log ~component:"netplex.controller" ~level:`Crit ~message:("Uncaught exception: " ^ Netexn.to_string error); run_controller ctrl ;; let get_config_pair ~config_parser par c_logger_cf c_wrkmg_cf c_proc_cf cf = let config_file = match cf.config_tree_opt with | None -> config_parser (config_filename cf) | Some tree -> Netplex_config.repr_config_file (config_filename cf) tree in let netplex_config = Netplex_config.read_netplex_config par#ptype c_logger_cf c_wrkmg_cf c_proc_cf config_file in (config_file, netplex_config) let handle_pidfile cf = match cf.pidfile with | Some file -> let f = open_out file in fprintf f "%d\n" (Unix.getpid()); close_out f; (fun () -> try Sys.remove file with _ -> ()) | None -> (fun () -> ()) let redirect_logger f = let old_logger = !Netlog.current_logger in let old_dlogger = !Netlog.Debug.current_dlogger in Netlog.current_logger := (fun level message -> try Netplex_cenv.log level message (* This function also works from the controller thread! *) with | Netplex_cenv.Not_in_container_thread -> (* Fall back to something safe: *) old_logger level message ); (* hmmm, Netlog.Debug cannot be handled by netplex *) Netlog.Debug.current_dlogger := (fun mname msg -> Netlog.channel_logger stderr `Debug `Debug (mname ^ ": " ^ msg) ); try let r = f() in Netlog.current_logger := old_logger; Netlog.Debug.current_dlogger := old_dlogger; r with | error -> Netlog.current_logger := old_logger; Netlog.Debug.current_dlogger := old_dlogger; raise error let setup_controller config_file netplex_config controller_config controller = let processors = List.map (fun (sockserv_cfg, (procaddr, c_proc_cfg), (wrkmngaddr, c_wrkmng_cfg) ) -> c_proc_cfg # create_processor controller_config config_file procaddr ) netplex_config#services in (* An exception while creating the processors will prevent the * startup of the whole system! *) let services = List.map2 (fun (sockserv_cfg, (procaddr, c_proc_cfg), (wrkmngaddr, c_wrkmng_cfg) ) processor -> try let wrkmng = c_wrkmng_cfg # create_workload_manager controller_config config_file wrkmngaddr in let sockserv = Netplex_sockserv.create_socket_service processor sockserv_cfg in Some (sockserv, wrkmng) with | error -> (* An error while creating the sockets is quite * problematic. We do not add the service, but we cannot * prevent the system startup at that late point in time *) controller # logger # log ~component:"netplex.controller" ~level:`Crit ~message:("Uncaught exception preparing service " ^ sockserv_cfg#name ^ ": " ^ Netexn.to_string error); None ) netplex_config#services processors in List.iter (function | Some(sockserv,wrkmng) -> ( try controller # add_service sockserv wrkmng with | error -> (* An error is very problematic now... *) controller # logger # log ~component:"netplex.controller" ~level:`Crit ~message:("Uncaught exception adding service " ^ sockserv#name ^ ": " ^ Netexn.to_string error); ) | None -> () ) services let startup ?(late_initializer = fun _ _ -> ()) ?(config_parser = Netplex_config.read_config_file) par c_logger_cf c_wrkmg_cf c_proc_cf cf = let (config_file, netplex_config) = get_config_pair ~config_parser par c_logger_cf c_wrkmg_cf c_proc_cf cf in let maybe_daemonize = (if cf.foreground then (fun f -> f ~init_done:(fun () -> ())) else daemon) in maybe_daemonize (fun ~init_done -> let remove_pid_file = handle_pidfile cf in try let controller_config = netplex_config # controller_config in let controller = Netplex_controller.create_controller par controller_config in Netplex_cenv.register_ctrl controller; (* Change to / so we don't block filesystems without need. Do this after controller creation so the controller has a chance to remember the cwd *) Unix.chdir "/"; (* FIXME Win32: Something like c:/ *) redirect_logger (fun () -> setup_controller config_file netplex_config controller_config controller; ( try late_initializer config_file controller with | error -> (* An error is ... *) controller # logger # log ~component:"netplex.controller" ~level:`Crit ~message:("Uncaught exception in late initialization: " ^ Netexn.to_string error); ); init_done(); run_controller controller; controller # free_resources(); Netplex_cenv.unregister_ctrl controller; remove_pid_file(); ) with | error -> remove_pid_file(); raise error ) ;; let run ?(config_parser = Netplex_config.read_config_file) ~late_initializer ~extract_result par c_logger_cf c_wrkmg_cf c_proc_cf cf = let (config_file, netplex_config) = get_config_pair ~config_parser par c_logger_cf c_wrkmg_cf c_proc_cf cf in let remove_pid_file = handle_pidfile cf in let cleanup = ref [ remove_pid_file ] in try let controller_config = netplex_config # controller_config in let controller = Netplex_controller.create_controller par controller_config in Netplex_cenv.register_ctrl controller; cleanup := (fun () -> Netplex_cenv.unregister_ctrl controller) :: !cleanup; redirect_logger (fun () -> setup_controller config_file netplex_config controller_config controller; cleanup := controller#free_resources :: !cleanup; let late_value = try late_initializer config_file controller with | error -> (* An error is ... *) controller # logger # log ~component:"netplex.controller" ~level:`Crit ~message:("Uncaught exception in late initialization: " ^ Netexn.to_string error); raise error in run_controller controller; let result = extract_result controller late_value in controller # free_resources(); Netplex_cenv.unregister_ctrl controller; remove_pid_file(); result ) with | error -> List.iter (fun f -> try f() with | e -> eprintf "Exception in cleanup after exception: %s\n%!" (Netexn.to_string e) ) !cleanup; raise error ;;