@@ -76,6 +76,23 @@ 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: " SERVER OPTIONS"
82+ ~env: (Cmd.Env. info " LEARNOCAML_SERVE_DURING_BUILD" ) ~doc:
83+ " If the directory specified by $(b,--app-dir) already exists from a \
84+ previous build, create a temporary child process to serve it \
85+ while the build completes, in order to reduce server downtime. \
86+ This flag requires to run both commands '$(b,learn-ocaml build serve)'. \
87+ After the build, the child process stops and a new server starts. \
88+ This flag is useful in a docker-compose context, and can be enabled \
89+ by adding to the environment: '$(env)=true'."
90+
91+ let child_pid =
92+ (* Note: option `--child-pid` is specific to the native learn-ocaml-server,
93+ hence this dummy value here, to avoid copying it in "SERVER OPTIONS". *)
94+ Term. const (None : int option )
95+
7996 module Grader = struct
8097 let info = info ~docs: " GRADER OPTIONS"
8198
@@ -263,17 +280,18 @@ module Args = struct
263280 app_dir : string ;
264281 repo_dir : string ;
265282 build_dir : string ;
283+ serve_during_build : bool ;
266284 grader : Grader .t ;
267285 builder : Builder .t ;
268286 server : Server .t ;
269287 }
270288
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 }
289+ let term child_pid =
290+ let apply commands app_dir repo_dir build_dir grader builder server serve_during_build =
291+ { commands; app_dir; repo_dir; build_dir; grader; builder; server; serve_during_build }
274292 in
275293 Term. (const apply $ commands $ app_dir $ repo_dir $ build_dir
276- $ Grader. term $ Builder. term $ Server. term app_dir base_url)
294+ $ Grader. term $ Builder. term $ Server. term app_dir base_url child_pid $ serve_during_build )
277295end
278296
279297open Args
@@ -319,7 +337,7 @@ let temp_app_dir o =
319337 ((basename o.app_dir) ^ " .temp" )
320338
321339let main o =
322- Printf. printf " Learnocaml v. %s running.\n %!" Learnocaml_api. version;
340+ Printf. printf " Learnocaml v%s running.\n %!" Learnocaml_api. version;
323341 let grade o =
324342 if List. mem Grade o.commands then
325343 (if List. mem Build o.commands || List. mem Serve o.commands then
@@ -391,23 +409,24 @@ let main o =
391409 end
392410 else Lwt. return_unit
393411 in
394- let generate o =
412+ let generate ?( check_port = true ) o =
395413 if List. mem Build o.commands then
396414 (let get_app_dir o =
397415 if not (List. mem Serve o.commands) then
398416 Lwt. return o.app_dir
399- else if o.server.Server. replace then
400- let app_dir = temp_app_dir o in
401- (if Sys. file_exists app_dir then
417+ else if o.server.Server. replace || o.serve_during_build then
418+ let temp_dir = temp_app_dir o in
419+ (if Sys. file_exists temp_dir then
402420 (Printf. eprintf " Warning: temporary directory %s already exists\n %!"
403- app_dir ;
421+ temp_dir ;
404422 Lwt. return_unit)
405423 else if Sys. file_exists o.app_dir then
406- Lwt_utils. copy_tree o.app_dir app_dir
424+ Lwt_utils. copy_tree o.app_dir temp_dir
407425 else
408426 Lwt. return_unit)
409- >> = fun () -> Lwt. return app_dir
410- else if Learnocaml_server. check_running () <> None then
427+ >> = fun () -> Lwt. return temp_dir
428+ else if check_port && Learnocaml_server. check_running () <> None then
429+ (* This server-specific check is here to fail earlier if need be *)
411430 (Printf. eprintf
412431 " Error: another server is already running on port %d \
413432 (consider using option `--replace`)\n %!"
@@ -500,12 +519,29 @@ let main o =
500519 else
501520 Lwt. return true
502521 in
503- let run_server o =
522+ let kill_once pid =
523+ let already = ref false in
524+ fun () ->
525+ if ! already then () else
526+ (already := true ;
527+ Unix. kill pid Sys. sigint;
528+ Printf. eprintf " Waiting for child process %d to terminate... %!" pid;
529+ ignore (Unix. waitpid [] pid);
530+ prerr_endline " ok " )
531+ in
532+ (* child_pid = None => no --serve-during-build
533+ child_pid = Some 0 => --serve-during-build, child process
534+ child_pid = Some n, n>0 => --serve-during-build, main process *)
535+ let run_server ~child_pid o =
504536 if List. mem Serve o.commands then
505537 let () =
506- if o.server.Server. replace then
507- let running = Learnocaml_server. check_running () in
508- Option. iter Learnocaml_server. kill_running running;
538+ let int_child_pid = Option. value child_pid ~default: (- 1 ) in
539+ if o.server.Server. replace || (o.serve_during_build && int_child_pid > 0 ) then
540+ let () =
541+ (if int_child_pid > 0 then kill_once int_child_pid ()
542+ else let running = Learnocaml_server. check_running () in
543+ Option. iter Learnocaml_server. kill_running running)
544+ in
509545 let temp = temp_app_dir o in
510546 let app_dir = absolute_filename o.app_dir in
511547 let bak =
@@ -542,6 +578,8 @@ let main o =
542578 (" --sync-dir=" ^ o.server.sync_dir) ::
543579 (" --base-url=" ^ o.builder.Builder. base_url) ::
544580 (" --port=" ^ string_of_int o.server.port) ::
581+ (match child_pid with None -> [] | Some n -> [" --child-pid=" ^ string_of_int n])
582+ @
545583 (match o.server.cert with None -> [] | Some c -> [" --cert=" ^ c])
546584 in
547585 Lwt. return
@@ -550,8 +588,13 @@ let main o =
550588 Unix. execv native_server
551589 (Array. of_list (native_server::server_args))))
552590 else begin
553- Printf. printf " Starting server on port %d\n %!"
554- ! Learnocaml_server. port;
591+ let comment = match child_pid with
592+ | None -> " "
593+ | Some 0 -> " (temporary)"
594+ | Some _pid -> " (main)"
595+ in
596+ Printf. printf " Starting server%s on port %d\n %!"
597+ comment ! Learnocaml_server. port;
555598 if o.builder.Builder. base_url <> " " then
556599 Printf. printf " Base URL: %s\n %!" o.builder.Builder. base_url;
557600 Learnocaml_server. launch () >> = fun ret ->
@@ -560,19 +603,57 @@ let main o =
560603 else
561604 Lwt. return (`Success true )
562605 in
606+ let lwt_run_server ~child_pid build_ok =
607+ if build_ok then
608+ run_server ~child_pid o >> = function
609+ | `Success true -> Lwt. return (`Code 0 )
610+ | `Success false -> Lwt. return (`Code 10 )
611+ | `Continuation f -> Lwt. return (`Continuation f)
612+ else
613+ Lwt. return (`Code 1 )
614+ in
615+ (* NOTE: the code below handles "learn-ocaml build serve --serve-during-build"
616+ by relying on Lwt_unix.fork; and to stay on the safe side, we make sure
617+ that this fork is triggered before the first Lwt_main.run command. *)
563618 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 ))
619+ if o.serve_during_build then begin
620+ if not (List. mem Build o.commands && List. mem Serve o.commands) then
621+ (Printf. eprintf
622+ " Error: option `--serve-during-build` requires both commands `build serve`.\n %!" ;
623+ exit 1 )
624+ else if o.server.Server. replace then
625+ (Printf. eprintf
626+ " Error: option `--replace` is incompatible with option `--serve-during-build`.\n %!" ;
627+ exit 10 )
628+ else if Learnocaml_server. check_running () <> None then
629+ (Printf. eprintf
630+ " Error: another server is already running on port %d \
631+ (consider using option `--replace` instead of `--serve-during-build`)\n %!"
632+ ! Learnocaml_server. port;
633+ exit 10 );
634+ match Lwt_unix. fork () with
635+ | 0 ->
636+ if Sys. file_exists o.app_dir then
637+ Lwt_main. run (lwt_run_server ~child_pid: (Some 0 ) true )
638+ else
639+ (Printf. eprintf
640+ " Info: no existing app-dir in '%s', \
641+ will be available at next run (skipping temporary server start).\n %!" o.app_dir;
642+ `Code 0 )
643+ | child_pid ->
644+ at_exit (kill_once child_pid);
645+ Lwt_main. run
646+ (grade o >> = function
647+ | Some i -> Lwt. return (`Code i)
648+ | None ->
649+ generate ~check_port: false o >> = lwt_run_server ~child_pid: (Some child_pid))
650+ end
651+ else
652+ Lwt_main. run
653+ (grade o >> = function
654+ | Some i -> Lwt. return (`Code i)
655+ | None ->
656+ generate o >> = lwt_run_server ~child_pid: None )
576657 in
577658 match ret with
578659 | `Code n -> exit n
@@ -627,7 +708,7 @@ let main_info =
627708 ~version: Learnocaml_api. version
628709 " learn-ocaml"
629710
630- let main_term = Term. (const main $ Args. term)
711+ let main_term = Term. (const main $ Args. term child_pid )
631712
632713let () =
633714 match
0 commit comments