## 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—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