aneris_examples.ccddb.examples.session_guarantees.sm_code

From iris Require Import invariants.
From iris.proofmode Require Import tactics.
From aneris.aneris_lang Require Import lang network notation tactics.
From aneris.aneris_lang.lib Require Import network_helpers lock assert.
From aneris.aneris_lang.lib.serialization Require Import serialization.
From aneris_examples.ccddb.spec Require Import base.
From aneris_examples.ccddb.examples Require Import res.

(* A simple session manager to reason about session guarantees *)
Section Code.
  Context `{!DB_params}.

  (* A version of listen_wait that additionally waits to receive a message
     with the given sequence id.
   *)

  Definition listen_wait_for_seqid : base_lang.val :=
    rec: "listen_wait_for_seqid" "sh" "seq_id" :=
        let: "resp_msg" := listen_wait "sh" in
        let: "resp" := DBS_deser resp_serialization (Fst "resp_msg") in
        let: "tag" := Fst "resp" in
        let: "val" := Snd "resp" in
        if: "tag" = !"seq_id" then
          ("seq_id" <- !"seq_id" + #1;;
          "val")
        else
          ("listen_wait_for_seqid" "sh" "seq_id").

  (*
   * Executes a request within a session.
   * The execution can be conceptualized as a
   * sequence of steps:
   *   - increment the sequence id
   *   - send an arbitrary message to a server
   *   - loop waiting for a reply from the server, using the sequence id to
   *     match the request we just sent to its response
   *   - return the server's response, minus the sequence id
   *)

  Definition session_exec : base_lang.val :=
    λ: "sh" "seq_id" "lock" "server_addr" "req",
      acquire "lock";;
      let: "msg" := DBS_ser req_serialization (!"seq_id", "req") in
      SendTo "sh" "msg" "server_addr";;
      let: "ret" := (listen_wait_for_seqid "sh" "seq_id") in
      release "lock";;
      "ret".

  (*
   * Initializes the session manager.
   * Returns a tuple `(init_fn, read_fn, write_fn)` of closures containing
   * the init_session, read and write operations, respectively.
   *
   * Throughout all operations, we maintain a _sequence id_, a monotonically
   * increasing integer that counts the number of requests sent to the server
   * so far in the session.
   * Calls to init_session, reads and writes all increase the sequence id.
   *
   * Inits, reads and writes are synchronized with a lock to guarantee that the
   * sequence id observed by an operation remains the maximum among all
   * previously-seen sequence ids, throughout the
   * execution of the operation. This is important because we communicate over
   * UDP so there are no duplicate protection or ordering guarantees.
   *)

  Definition sm_setup : base_lang.val :=
    λ: "client_addr",
      let: "socket" := NewSocket #Network.PF_INET
                                 #Network.SOCK_DGRAM
                                 #Network.IPPROTO_UDP in
      SocketBind "socket" "client_addr";;
      let: "seq_id" := ref #0 in
      let: "lock" := newlock #() in

      (* Initializes a session with the replica at `server_addr`. *)
      let: "init_fn" :=
         λ: "server_addr",
           session_exec "socket" "seq_id" "lock" "server_addr" (InjL #"I");;
           #()
      in
      (* Reads the value of `key` from the replica at `server_addr`. *)
      let: "read_fn" :=
         λ: "server_addr" "key",
         let: "res" := session_exec "socket" "seq_id" "lock" "server_addr" (InjR (InjL "key")) in
         match: "res" with
           InjL "x" => assert #false
         | InjR "res'" =>
           match: "res'" with
             InjL "vo" =>
             match: "vo" with
               InjL "x" => NONEV
             | InjR "v" => (SOME "v")%V
             end
           | InjR "x" => assert #false
           end
         end
      in
      (* Writes `val` to `key` in the replica at `server_addr`. *)
      let: "write_fn" :=
         λ: "server_addr" "key" "val",
          session_exec "socket" "seq_id" "lock" "server_addr" (InjR (InjR ("key", "val")));;
          #()
      in
      ("init_fn", "read_fn", "write_fn").

End Code.