How to run a Deferred.t?

async
core

#1

Hi,

I’m wondering how do I start the Async scheduler, run a deferred and then stop it again (terminating my program).

My current code is this:

let () =
  Thread_safe.block_on_async_exn @@ fun () ->
    after (Core.Std.Time.Span.of_sec 1.) >>= fun _ ->
    print_endline "hi";
    Deferred.return ()

Which would have been perfect, but it is very unreliable. Sometimes it works (when the amount of secs is for example 0, sometimes it fails and does nothing.

What is the proper way for this to be done?


#2

I’ve found this “solution”, which is slightly more reliable, but I’m not entirely happy with it either:

let () =
  let d =
    after (Core.Std.Time.Span.of_sec 5.) >>= fun _ ->
    print_endline "hi";
    Deferred.return ()
  in
  Deferred.upon d (fun () -> Shutdown.shutdown 0);
  Core_kernel.Std.never_returns @@ Scheduler.go ()

But at least the go and shutdown are close to each other.


#3

This should do the trick:

open! Core
open! Async

let () =
  Thread_safe.block_on_async_exn (fun () ->
      let%bind () = after (Time.Span.of_sec 1.) in
      print_endline "hi";
      Writer.flushed (force Writer.stdout)
    )

The key change is waiting for stdout to be flushed.


#4

Thanks! That works. Is there some documentation on this, because Thread_safe is kinda not a module which I would have checked for running a Deferred.t.

(Late response because my laptop got stolen)


#5

It’s worth saying that this is something of an unidiomatic way of writing the code. A more ordinary mode would be:

open! Core
open! Async

let () =
   don't_wait_for begin
       let%map () = after (Time.Span.of_sec 1.) in
       print_endline "hi"
   end

let () = never_returns (Scheduler.go ())

The scheduler makes an attempt to wait for pending writes to finish before executing, so you shouldn’t need to add the explicit Writer.flush, though you can if you want to make sure it gets out the door.

You can learn more about all this in RWO: http://dev.realworldocaml.org/18-concurrent-programming.html


#6

Yes sure, that I saw in RWO, but it is not great to have your simple wget-like program do the work and then stay in the even loop forever (so you have to ^C it), because the scheduler is awaiting for more Deferred.ts which will never arrive because the HTTP request you wanted to do is done already.

I am kinda surprised that the one-shot way does not seem to be documented anywhere, because I can’t believe I am the only person who wants to use Async for a small command-line client.


#7

You can end the program easily enough:

open! Core
open! Async

let () =
   don't_wait_for begin
       let%map () = after (Time.Span.of_sec 1.) in
       print_endline "hi";
       shutdown 0
   end

let () = never_returns (Scheduler.go ())

Also, if you use Command.async to set up your command-line API, it will automatically start the scheduler for you and shut it down when the deferred returned by your program logic returns. Within Jane Street’s walls, that’s the way we solve the command-line program problem. Presumably a small wrapper could be written for commandliner which gives similar functionality.


#8

Sure, I can shutdown the scheduler from inside a Deferred but this creates too tight coupling because then the deferred needs to know that I want to shut down the system and can’t be composed. Or I could add another Deferred whose sole purpose is to shutdown but that strikes me as a very roundabout way, more like fighting the API instead of working with it.

Thanks for the suggestion for Command.async, that is probably the best way for me to go about it. The hypothetical wrapper for cmdliner would probably look like the code you proposed in your initial answer, right?


#9

No, it looks more like the code in my latest answer. There’s a bunch of careful shutdown code in Async_extra.Command for this, which you can find here:

It’s possible we should factor that out and put it in a place that’s easier to share with something like cmdliner.