@@ -76,6 +76,22 @@ module Args = struct
7676 Mandatory for '$(b,learn-ocaml build)' if the site is not hosted in path '/', \
7777 which typically occurs for static deployment."
7878
79+ let serve_during_build =
80+ value & flag &
81+ info [" serve-during-build" ] ~docs ~env: (Cmd.Env. info " LEARNOCAML_SERVE_DURING_BUILD" ) ~doc:
82+ " If the $(i,--app-dir) directory already exists from a previous build, \
83+ create a child process thanks to $(i,Unix.fork) to serve this version \
84+ during the build, reducing server's downtime as much as possible. \
85+ This flag requires to run both commands '$(b,learn-ocaml build serve)'. \
86+ After the build, the child process stops and a new server starts. \
87+ This flag is useful in a docker-compose context, and can be enabled \
88+ by adding to the environment: '$(i,LEARNOCAML_SERVE_DURING_BUILD=true)'."
89+
90+ let child_pid =
91+ (* Note: option `--child-pid` is specific to the native learn-ocaml-server,
92+ hence this dummy value here, to avoid copying it in "SERVER OPTIONS". *)
93+ Term. const (- 2 )
94+
7995 module Grader = struct
8096 let info = info ~docs: " GRADER OPTIONS"
8197
@@ -263,17 +279,18 @@ module Args = struct
263279 app_dir : string ;
264280 repo_dir : string ;
265281 build_dir : string ;
282+ serve_during_build : bool ;
266283 grader : Grader .t ;
267284 builder : Builder .t ;
268285 server : Server .t ;
269286 }
270287
271- let term =
272- let apply commands app_dir repo_dir build_dir grader builder server =
273- { commands; app_dir; repo_dir; build_dir; grader; builder; server }
288+ let term child_pid =
289+ let apply commands app_dir repo_dir build_dir grader builder server serve_during_build =
290+ { commands; app_dir; repo_dir; build_dir; grader; builder; server; serve_during_build }
274291 in
275292 Term. (const apply $ commands $ app_dir $ repo_dir $ build_dir
276- $ Grader. term $ Builder. term $ Server. term app_dir base_url)
293+ $ Grader. term $ Builder. term $ Server. term app_dir base_url child_pid $ serve_during_build )
277294end
278295
279296open Args
@@ -391,12 +408,12 @@ let main o =
391408 end
392409 else Lwt. return_unit
393410 in
394- let generate o =
411+ let generate ?( check_port = true ) o =
395412 if List. mem Build o.commands then
396413 (let get_app_dir o =
397414 if not (List. mem Serve o.commands) then
398415 Lwt. return o.app_dir
399- else if o.server.Server. replace then
416+ else if o.server.Server. replace || o.serve_during_build then
400417 let temp_dir = temp_app_dir o in
401418 (if Sys. file_exists temp_dir then
402419 (Printf. eprintf " Warning: temporary directory %s already exists\n %!"
@@ -407,7 +424,8 @@ let main o =
407424 else
408425 Lwt. return_unit)
409426 >> = fun () -> Lwt. return temp_dir
410- else if Learnocaml_server. check_running () <> None then
427+ else if check_port && Learnocaml_server. check_running () <> None then
428+ (* This server-specific check is here to fail earlier if need be *)
411429 (Printf. eprintf
412430 " Error: another server is already running on port %d \
413431 (consider using option `--replace`)\n %!"
@@ -500,12 +518,27 @@ let main o =
500518 else
501519 Lwt. return true
502520 in
503- let run_server o =
521+ let kill_once pid =
522+ let already = ref false in
523+ fun () ->
524+ if ! already then () else
525+ (already := true ;
526+ Learnocaml_server. kill_running pid;
527+ ignore (Unix. waitpid [Unix. WNOHANG ] pid))
528+ in
529+ (* child_pid = None => no --serve-during-build
530+ child_pid = Some 0 => --serve-during-build, child process
531+ child_pid = Some n, n>0 => --serve-during-build, main process *)
532+ let run_server ~child_pid o =
504533 if List. mem Serve o.commands then
534+ let int_child_pid = Option. value child_pid ~default: (- 1 ) in
505535 let () =
506- if o.server.Server. replace then
507- let running = Learnocaml_server. check_running () in
508- Option. iter Learnocaml_server. kill_running running;
536+ if o.server.Server. replace || (o.serve_during_build && int_child_pid > 0 ) then
537+ let () =
538+ (if int_child_pid > 0 then kill_once int_child_pid ()
539+ else let running = Learnocaml_server. check_running () in
540+ Option. iter Learnocaml_server. kill_running running)
541+ in
509542 let temp = temp_app_dir o in
510543 let app_dir = absolute_filename o.app_dir in
511544 let bak =
@@ -542,6 +575,7 @@ let main o =
542575 (" --sync-dir=" ^ o.server.sync_dir) ::
543576 (" --base-url=" ^ o.builder.Builder. base_url) ::
544577 (" --port=" ^ string_of_int o.server.port) ::
578+ (" --child-pid=" ^ string_of_int int_child_pid) ::
545579 (match o.server.cert with None -> [] | Some c -> [" --cert=" ^ c])
546580 in
547581 Lwt. return
@@ -550,8 +584,13 @@ let main o =
550584 Unix. execv native_server
551585 (Array. of_list (native_server::server_args))))
552586 else begin
553- Printf. printf " Starting server on port %d\n %!"
554- ! Learnocaml_server. port;
587+ let comment = match int_child_pid with
588+ | - 1 -> " "
589+ | 0 -> " (child)"
590+ | _pid -> " (main)"
591+ in
592+ Printf. printf " Starting server%s on port %d\n %!"
593+ comment ! Learnocaml_server. port;
555594 if o.builder.Builder. base_url <> " " then
556595 Printf. printf " Base URL: %s\n %!" o.builder.Builder. base_url;
557596 Learnocaml_server. launch () >> = fun ret ->
@@ -560,19 +599,57 @@ let main o =
560599 else
561600 Lwt. return (`Success true )
562601 in
602+ let lwt_run_server ~child_pid build_ok =
603+ if build_ok then
604+ run_server ~child_pid o >> = function
605+ | `Success true -> Lwt. return (`Code 0 )
606+ | `Success false -> Lwt. return (`Code 10 )
607+ | `Continuation f -> Lwt. return (`Continuation f)
608+ else
609+ Lwt. return (`Code 1 )
610+ in
611+ (* NOTE: the code below handles "learn-ocaml build serve --serve-during-build"
612+ by relying on Lwt.fork; and to stay on the safe side, we make sure
613+ that this fork is triggered before the first Lwt_main.run command. *)
563614 let ret =
564- Lwt_main. run
565- (grade o >> = function
566- | Some i -> Lwt. return (`Code i)
567- | None ->
568- generate o >> = fun success ->
569- if success then
570- run_server o >> = function
571- | `Success true -> Lwt. return (`Code 0 )
572- | `Success false -> Lwt. return (`Code 10 )
573- | `Continuation f -> Lwt. return (`Continuation f)
574- else
575- Lwt. return (`Code 1 ))
615+ if o.serve_during_build then begin
616+ if not (List. mem Build o.commands && List. mem Serve o.commands) then
617+ (Printf. eprintf
618+ " Error: option `--serve-during-build` requires both commands `build serve`.\n %!" ;
619+ exit 1 )
620+ else if o.server.Server. replace then
621+ (Printf. eprintf
622+ " Error: option `--replace` is incompatible with option `--serve-during-build`.\n %!" ;
623+ exit 10 )
624+ else if Learnocaml_server. check_running () <> None then
625+ (Printf. eprintf
626+ " Error: another server is already running on port %d \
627+ (consider using option `--replace`)\n %!"
628+ ! Learnocaml_server. port;
629+ exit 10 );
630+ match Lwt_unix. fork () with
631+ | 0 ->
632+ if Sys. file_exists o.app_dir then
633+ Lwt_main. run (lwt_run_server ~child_pid: (Some 0 ) true )
634+ else
635+ (Printf. eprintf
636+ " Info: no existing app-dir in '%s', \
637+ will be available at next run (skipping child server start).\n %!" o.app_dir;
638+ `Code 0 )
639+ | child_pid ->
640+ at_exit (kill_once child_pid);
641+ Lwt_main. run
642+ (grade o >> = function
643+ | Some i -> Lwt. return (`Code i)
644+ | None ->
645+ generate ~check_port: false o >> = lwt_run_server ~child_pid: (Some child_pid))
646+ end
647+ else
648+ Lwt_main. run
649+ (grade o >> = function
650+ | Some i -> Lwt. return (`Code i)
651+ | None ->
652+ generate o >> = lwt_run_server ~child_pid: None )
576653 in
577654 match ret with
578655 | `Code n -> exit n
@@ -627,7 +704,7 @@ let main_info =
627704 ~version: Learnocaml_api. version
628705 " learn-ocaml"
629706
630- let main_term = Term. (const main $ Args. term)
707+ let main_term = Term. (const main $ Args. term child_pid )
631708
632709let () =
633710 match
0 commit comments