Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Nanotransactions

A nanotransaction is any function operating on invariant, mobile data that does not include another transactional function invocation as part of its body.

One of the simplest nanotransactions one can write is a function that transfers a certain amount of money between users' bank accounts:

#![allow(unused)]
fn main() {
let transfer = nando(|
  src: &Account,
  dst: &Account,
  amount: f64,
| {
  if src.balance < amount {
    abort(/* error code */);
  }

  src.balance -= amount;
  dst.balance += amount;
})
}

A nanotransaction is executed in an all-or-nothing fashion, with its updates made visible at commit time. The application of an authored nanotransaction f on a set of input and output objects o_1, ..., o_n is referred to as an activation of nanotransaction f.

Importantly, a nanotransaction never invokes another nanotransaction -- nanotransactions containing nanotransaction invocations in their body will be decomposed into epics.

Iterations

Lifetime of a nanotransaction activation

A request for an activation originates on some machine, either because of a user explicitly making a call through an interactive shell, or programmaticaly by a program condition in the ephemeral context of a user program (running on top of Magpie) invoking a nanotransaction with concrete object IDs.

The first step of execution involves the source machine writing down the activation in a local intent log. QUESTION should this be done before or after scheduling? If we do it before scheduling, it meanst that the most information we can store at this point is the "transaction id" (a content-based hash of the function to run and its input arguments). If we do it after scheduling, we can breadcrumb our way through the execution if it spills over to a different machine if we ever have to inquire about the status of the nanotransaction execution.

Some scheduling needs to take place. We inspect the set of arguments, we consult the ownership module, and come up with an activation location. The second step is to orchestrate all the required data movement to the activation location, submit the nanotransaction to the activation location, and (potentially) await for the result.

Logging

What we keep track of, and why

Intent logging: Each transaction should have an intent entry in the intent log specifying the system's intent to execute it. For each successful commit, there should also be a corresponding committed entry (QUESTION how does this work with compound transactions? multi-host transactions?).

Old-value logging: keep track of the values of the fields that the nanotransaction we are about to run is going to overwrite (by assigning to them), as tracked through this change in the nandoize macro. The reason for tracking old values is so that, on abort, we can undo a transaction's (partial) changes to provide failure atomicity.

New-value logging: keep track of the values that this transaction ended up writing (and to which object fields) so that, on recovery, we can re-execute them.