14 views
Skip to first unread message

Ashish Agarwal

unread,
Nov 18, 2015, 2:27:08 PM11/18/15
to ocaml...@googlegroups.com
On Wed, Nov 18, 2015 at 10:46 AM, Arseniy Alekseyev <aalek...@janestreet.com> wrote:

I think starting the scheduler in the child after the fork is fine as long as you don't inherit an existing (now broken) scheduler from the parent process.

Is it broken in the case that it was already started, or in some other sense too? Are you suggesting that `reset_in_forked_process` really *must* be called? I don't understand why this function would be useful to call. Either we follow the rule that the scheduler should not have been started before the fork, in which case there is no work for this function to avoid inheriting, or maybe it is okay to have some work done before, in which case why would one want to forget about it.


Indeed, [main1 ()] should run and it does. However, concurrently the following happens: the result of [main2] becomes determined (note the [return] you use there); this makes [async'] think that your program is done and it exits. Of course this wins the race against the 1 second delay in [main1] so the message never gets printed. If you don't want [async'] to kill your program prematurely, you can try using e.g. [fun () -> main2 () >>= fun () -> Deferred.never ()] instead of [main2].

I see. Thank you for the explanation. However the solution seems roundabout. We're first calling Command.async', which starts the scheduler and does a shutdown for you, but then tricking it into not shutting down by doing Deferred.never. I haven't come up with an alternative though.

 
Maybe you didn't realize that [async'] runs the scheduler for you?

I did but ...
 

This error probably happens because you *also* start the scheduler before the fork.

In the following code:

let ()  =
  upon (return () >>| ... Core.Std.Unix.fork() ...) (fun () -> ())
  ; never_returns (Scheduler.go())

is the scheduler getting started before or after the fork? I thought it wasn't deterministic. Doesn't async "maybe" start jobs right away at creation time, and not wait for the scheduler? In any case, it is certainly the case that the scheduler may get started before the fork, so this is where my error in reasoning was.

 
If so, the solution is: don't use [async'] and stick to [basic'].

This is the last missing piece. If I need code like:

return () >>| ... Unix.fork() ...

then I end up with a Deferred, and I want to create a Command with that. I don't see how to do that while maintaining the other requirements, except perhaps to user your Deferred.never hack above. I'm trying it.

Thanks for all the help!

Feature Request: A function like Process.create, but which takes f:unit -> unit Deferred.t instead of prog:string and args:string list.

Stephen Weeks

unread,
Nov 18, 2015, 2:46:33 PM11/18/15
to ocaml...@googlegroups.com
Creating the scheduler and starting the scheduler are different
things. [reset_in_forked_process] works if you have created the
scheduler, but not started it.

> In the following code:
>
> let () =
> upon (return () >>| ... Core.Std.Unix.fork() ...) (fun () -> ())
> ; never_returns (Scheduler.go())
>
> is the scheduler getting started before or after the fork?

Before.

> Feature Request: A function like Process.create, but which takes
> f:unit -> unit Deferred.t instead of prog:string and args:string
> list.

This is unlikely to happen in the foreseeable future.

Ashish Agarwal

unread,
Nov 18, 2015, 3:00:16 PM11/18/15
to ocaml...@googlegroups.com
On Wed, Nov 18, 2015 at 2:46 PM, Stephen Weeks <swe...@janestreet.com> wrote:

> Feature Request: A function like Process.create, but which takes
> f:unit -> unit Deferred.t instead of prog:string and args:string
> list.

This is unlikely to happen in the foreseeable future.

Is that because it is hard to do, or just no use case for Jane Street?

Stephen Weeks

unread,
Nov 18, 2015, 3:04:29 PM11/18/15
to ocaml...@googlegroups.com
> Is that because it is hard to do, or just no use case for Jane
> Street?

Hard.

Ashish Agarwal

unread,
Nov 18, 2015, 3:06:30 PM11/18/15
to ocaml...@googlegroups.com
Okay, thanks. I'm glad to have confirmation that what I'm trying is tricky, and not just due to ignorance.  :)


--
You received this message because you are subscribed to the Google Groups "ocaml-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ocaml-core+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Nick Chapman

unread,
Nov 19, 2015, 4:27:46 AM11/19/15
to ocaml...@googlegroups.com

Hi Ashish,

I realise I'm entering this conversation a bit late... but it is possible to get your
example working without any dark magic.

Aside: Use of blocking printf is confusing unless a flush is forced with [%!]:
  let printf fmt = ksprintf (Core.Std.Printf.printf "%s%!") fmt

In your example, we want to [main2] to be composed as sleep-then-[main1], whilst ensuring
the fork occurs before starting the async scheduler. Consequently, we mustn't call [fork]
from [main1], but instead can define [run] to co-ordinate the fork and [Scheduler.go].

Once we ensure the async scheduler is started correctly in parent & child, Async's printf
works just fine, and no explicit flush is required.

Nick.


open Core.Std
open Async.Std

let main1 ~where () : unit Deferred.t  =
  match where with
  | `In_the_parent child_pid -> (
    let child_pid = Pid.to_int child_pid in
    let pid = Unix.getpid() |> Pid.to_int in
    printf "START: In the parent %d of child %d\n" pid child_pid;
    after (sec 1.) >>= fun () ->
    printf "END: In the parent %d of child %d\n" pid child_pid;
    shutdown 0;
    Deferred.unit
  )
  | `In_the_child -> (
    let ppid = Unix.getppid() |> Option.value_map ~default:0 ~f:Pid.to_int in
    let pid = Unix.getpid() |> Pid.to_int in
    printf "START: In the child %d with parent %d\n" pid ppid;
    after (sec 1.) >>= fun () ->
    printf "END: In the child %d with parent %d\n" pid ppid;
    shutdown 42; (* no-one collects this exit code *)
    Deferred.unit
  )

let main2 ~where () : unit Deferred.t =
  after (sec 1.) >>= fun () ->
  main1 ~where ()

let run main () : unit =
  let where = Core.Std.Unix.fork() in
  don't_wait_for (main ~where ());
  never_returns (Scheduler.go())

let cmd1 = Command.basic'
  ~summary:"run main1" Command.Param.nil (run main1)

let cmd2 = Command.basic'
  ~summary:"run main2" Command.Param.nil (run main2)

let () = Command.group
  ~summary:"fork + async example"
  ["cmd1",cmd1; "cmd2",cmd2]
  |> Command.run

--
Nick Chapman <n...@amadido.co.uk>
Beira Cottage, The Phygtle, Chalfont St. Peter, Bucks, SL9 0JT, UK.
+44 1494 876885 (home)  +44 7779 121533 (mobile)

Ashish Agarwal

unread,
Nov 19, 2015, 11:01:23 AM11/19/15
to ocaml...@googlegroups.com
So simple. Thank you!
Reply all
Reply to author
Forward
0 new messages