## Async Warmup Read the following code:  open Async.Std (* do some meaningless computation for about 5 seconds, depending on machine speed *) let busywait = let ctr = ref 0 in fun () -> ctr := 0; for i = 1 to 500_000_000 do incr ctr done (* helper function to print a string *) let output s = Printf.printf "%s\n" s let _ = output "A" let d = return 42 let _ = output "B" let _ = upon d (fun n -> output "C") let _ = output "D"  **Exercise:** Before trying to run that code, first think about an answer to this question: in what order will the four strings be output? Write down a guess before proceeding. **Exercise:** Now put the above code in a file named warmup.ml. Compile and run it with  $cs3110 compile -t -p async warmup.ml$ cs3110 run warmup  What output do you get? Does it match what you guessed? **Exercise:** The reason why C is missing is that the scheduler is not running. The callback that is registered with upon gets to run only if the scheduler actually schedules it. So add this as the last line of the file:  let _ = Scheduler.go()  Recompile and run. What happens? **Exercise:** The reason you don't get any output has to do with *buffering*. The output has been sent off to be printed, but is still temporarily sitting in a buffer that hasn't yet been printed. To force the buffer to be cleared, change the format specifier passed to printf to be "%s\n%!". The %! forces the buffer to be cleared. Recompile and run. What happens? **Exercise:** You should now see all four strings output. But they might be in a different order than you guessed. The callback that outputs C is registered, then D is output, then the scheduler starts. Only at that point can the callback be scheduled and run. So D is printed before C. Then the scheduler keeps running, though it has nothing more to do. To terminate the program, you'll have to enter Control-C. **Exercise:** Add a call to busywait () in various places in the program, recompile, and run, to see what effects it has. Try at least these places: * busywait(); output "A" * return (busywait (); 42) * busywait(); output "C" * busywait(); Scheduler.go() Explain in words what you observe and why. ## Bind So far we've seen one way of registering a callback with the Async scheduler: use  upon : 'a Deferred.t -> ('a -> unit) -> unit  The callback gets to use the contents of the deferred, but the callback doesn't return anything interesting&mdash;just (), which is consumed by the scheduler. What if you want the callback to produce some interesting value that can be used by the rest of the computation? There's a way to register that kind of callback with a function  Deferred.bind : 'a Deferred.t -> ('a -> 'b Deferred.t) -> 'b Deferred.t  This function is used to schedule a deferred computation to take place *after* an existing deferred computation finishes. The function bind takes two inputs: a deferred d : 'a Deferred.t, and a callback c : ('a -> 'b Deferred.t). The expression bind d c immediately returns with a new deferred d'. Sometime after d is determined (if ever), the scheduler runs c on the contents of d. The callback c itself produces a new deferred, which if it ever becomes determined, also causes d' to be determined with the same value. **Exercise:** Enter the following code in a file:  open Async.Std let output s = Printf.printf "%s\n%!" s let fc = Reader.file_contents Sys.argv.(1) let uc = Deferred.bind fc (fun s -> return (String.uppercase s)) let _ = upon uc (fun s -> output s) let _ = Scheduler.go ()  The code creates a deferred fc that will become determined after the contents of a file (named by the first command-line argument) have been read. The code then registers a callback to be run when those contents are available. That callback returns a deferred that is immediately determined and is the upper-cased version of the file. The code finally registers another callback to print those uppercased contents. **Exercise:** Remove the return function call from the above code and attempt to compile it. Why does compilation fail? Why is return needed? **Exercise:** Once again, you will need to exit the above program with Control-C. That's because the scheduler is still running, even though there's nothing left to do. Change the call to output as follows:  output s; ignore(exit 0)  Recompile and run. The program should now terminate when the file contents have been uppercased and printed. The function exit : int -> 'a Deferred.t is provided by Async.Std and causes execution to terminate. The function ignore : 'a -> unit is provided by Pervasives. ## Threads * Write a program that creates a single thread, which prints "Goodbye, OCaml". Hint: if your program does not produce any output, you probably forgot to join the thread. * Modify the previous program to create three threads, each of which prints "Goodbye, OCaml, from thread n", where n is the thread's *id* as determined by Thread.id. See the [Thread module documentation][Thread] for more details. Each thread's id should be unique. Run the program many times to see that the output is nondeterministic. Consider the following code:  let ctr = ref 0 let inc_lots () = for i=1 to 100_000_000 do incr ctr done let print_ctr () = Printf.printf "Counter = %d" (!ctr)  This code creates a counter named ctr, and provides two functions: inc_lots, which increments the counter many times, and print_ctr, which prints the value of the counter. * Using the above code, write a program that creates two threads, one of which calls inc_lots, and the other calls print_ctr. Run the program many times to see that the output is nondeterministic. * Modify the previous program to use a Mutex to ensure that the output is always either 0 or 100_000_000 but never anything in between. [Thread]: http://caml.inria.fr/pub/docs/manual-ocaml/libref/Thread.html ## * Monitor a file Write an Async program that monitors the contents of a log file. Specifically, your program should open the file, continually read a line from the file, and as each line becomes available, print the line to stdout. When you reach the end of the file (EOF), your program should terminate. To get started, open a new terminal window and enter the following commands:  $mkfifo log$ cat >log  Now anything you type into the terminal window (after pressing return) will be added to the log file. This will enable you to test your program interactively. Next, enter the following code into a file named monitor.ml:  open Async.Std let rec monitor file = (* hint: use Reader.read_line *) let _ = Reader.open_file "log" ... (* hint: use bind and monitor *) let _ = Scheduler.go ()  Complete the code by filling in the sections marked .... [Reader.read_line and Reader.open_file are documented in the Async documentation][reader]. To pattern match against the result of Reader.read_line, you need to write a pattern like the following:  | Ok line -> (* do something with the line, which is a string *) | Eof -> (* do something because the end of file is reached *)  [reader]: https://ocaml.janestreet.com/ocaml-core/111.28.00/doc/async/#Std.Reader [async-doc]: https://ocaml.janestreet.com/ocaml-core/111.28.00/doc/async/#Std