% to get hardcopy of this lecture, you need the following files (plus the
% normal latex and tex base directories):
%     lecture.tex:     latex source file
%     macros.tex:      figure macros
%     psfig.tex:       postscript figure (psfig) macro definition
%     sort.idraw:      first figure, created with idraw
%     security.idraw:  second figure, created with idraw
%     proof.idraw:     third figure, created with idraw
%     pocs-header.sty: Principles of Computer Systems lecture latex header
%
% run the following programs:
% latex lecture
%   -> Creates lecture.dvi, plus latex intermediate files
% latex lecture
%   -> Run a second time to get cross-references right
% dvi2ps lecture > lecture.ps
%   -> Merges the figures with the text, can also pipe to lpr
%
\documentstyle[12pt,pocs-header]{article}
\Scribes{Pegor Papazian and Andrew Heybey}
\Lecturer{William Weihl}
\LectureNumber{13}
\LectureDate{October 29, 1990}
%\include{macros} % used to include figures in this document
\begin{document}
\MakeScribeTop

\section{Administrata}

\begin{tabular}{|c|l|} \hline
Handout & \multicolumn{1}{c|}{Title} \\\hline
25      & Concurrent Transactions \\\hline

26      & Reading: "Recovery for Shared Disk Systems Using Multiple Redo Logs" \\
        & by David B. Lomet \\\hline
27      & Problem Set \#5 \\\hline
Notes   & Lecture 11 scribe notes. \\\hline
\end{tabular}

\section{{\tt Inverse} Revisited}

In the previous lecture we saw the {\tt Inverse} procedure from handout 24
which specifies an inverse action which works in all possible states.
This is problematic because many interesting operations do not have an
inverse in all states.

A procedure which changes a person's address in a database (like
over-writing operations in general) does not have an inverse in just
any state. Therefore the {\tt Inverse} in handout 24 should be modified to
take a state as one of its arguments:

\begin{verbatim}
Proc Inverse (a,s) -> UN =
     << VAR un | (ALL s',u' | (u',s') = a.meaning(s)
                                        => (nil,s) = u.meaning(s'))
        => RET un >>
\end{verbatim}

We will always apply an {\tt Inverse} in the appropriate state where it can
do the right thing.

\section{Concurrent Transactions}

Transactions were traditionally developed to handle concurrency,
although they are useful for dealing with crashes as well. Suppose
several people are running the following lines of code concurrently:

\begin{verbatim}
        bal := acct(person).bal
        acct(person).bal := bal + ammt
\end{verbatim}

Obviously some updates can be lost due to the interleaving of
concurrent threads. To solve this problem we can use synchronization
or we can rely on the database for control. But if we are writing a
database facility for instance, where we cannot anticipate what
operations the user will want to consider atomic, then we need
something more powerful, namely transactions.

\begin{tabbing}
Transactions can be viewed as critical sections in a program: \\
\\
Begin(transaction) $\ldots$ End(transaction) \\
\\
is much like the following: \\
\\
Acquire(mutex) $\ldots$ Release(mutex) \\
\\
However transactions, unlike mutexes, are (appear) atomic in the face of crashes as well. \\
\end{tabbing}

\subsection{Informal Semantics}

The {\tt Begin} procedure of handout 25 chooses a transaction id. This
id will be used when calling {\tt Do} and {\tt Commit}. It is a good
idea to have a separate thread per transaction. {\tt Abort} aborts a
transaction and {\tt Crash} aborts all transactions.

A difference between sequential transactions and concurrent ones is
that in the concurrent case {\tt Do} and {\tt Commit} can raise
aborted themselves. This happens when there is a deadlock (another
advantage of transactions is that they are convenient for backing out
of deadlocks), or when part of a distributed system goes down. Notice
however that in the {\tt ConcurrentTransactions} module of handout 25
there is no way of having a handle on the processes making use of the
procedures in the module. So any decision to "try again" after an
internal abort must be taken at a higher level.

\subsection{Desired Behavior}

We want concurrent transaction to guarantee at least the following:

\begin{itemize}
\item Committed transaction should be atomic.
\item Aborted transaction should have no effect.
\item Committed transactions should survive crashes.
\end{itemize}

The following are three criteria commonly expected of transactions:

\begin{enumerate}
\item Serializability: Externally, transactions should appear to have
occurred in some non-interleaved order.

\item Recoverability: This is an ``all-or-nothing'' principle. It should
either appear as if a transaction ran completely, or that it did not
get started at all.

\item External Consistency: The order of the serialization should be
consistent with the order of {\tt Begin}s and {\tt Commit}s. It would not be
acceptable, for instance, to have the apparent serialization T2 T1 in
the following situation:

\begin{tabbing}
\hspace{.8in}\= \kill
\>      Begin (T1) \\
\>      Commit (T1) \\
\>      Begin (T2) \\
\>      Commit (T2) \\
\\
However, we will accept an arbitrary order of serialization in the following case: \\
\\
\>\hspace{.8in}\= \kill
\>      Begin (T1) \\
\>\>            Begin (T2) \\
\>\>            Commit (T2) \\
\>      Commit (T1) \\
\end{tabbing}
\end{enumerate}

In the {\tt ConcurrentTransactions} module, the variable of type {\tt
PO} keeps track of the partial order which represents the desired
serialization.

\section{Two-phase Locking}

One way to externally consistent transactions is to use two-phase
locking.  As one might expect, each transaction runs in two phases:

\begin{enumerate}
\item The transaction acquires its locks.
\item The transaction releases its locks.
\end{enumerate}

In other words, once the transaction releases a lock, it will not
attempt to acquire any more locks.  It is easy to show that the
committed transactions are serializable:

\begin{tabbing}
\hspace{.8in}\= \kill
\>      $L(x), T_1$ \\
\>      $A_1(x), T_1$ \\
\>      $L(y), T_2$ \\
\>      $A_2(y), T_2$ \\
\>      $L(z), T_1$ \\
\>      $A_3(z), T_1$ \\
\>\hspace{2em}\= \kill
\>\>      . \\
\>\>      . \\
\>\>      . \\
\>      {\it UL}$(y), T_2$ \\
\>\>      . \\
\>\>      . \\
\>\>      . \\
\>      {\it UL}$(z), T_1$ \\
\>      {\it UL}$(x), T_1$ \\
\end{tabbing}
has the same external effect as executing the transactions serially
sorted by the order of their first unlock:

\begin{tabbing}
\hspace{.8in}\= \kill
\>      $L(y), T_2$ \\
\>      $A_2(y), T_2$ \\
\>      {\it UL}$(y), T_2$ \\
\>      $L(x), T_1$ \\
\>      $A_1(x), T_1$ \\
\>      $L(z), T_1$ \\
\>      $A_3(z), T_1$ \\
\>      etc. \\
\end{tabbing}

Having to deal with transaction aborts adds complexity.  In the
following sequence:
\begin{tabbing}
$L(x), T_1$;  $A_1(x), T_1$;  $UL(x), T_1$;  Abort($T_1$)\\
\end{tabbing}
what if $T_2$ reads $x$ after $T_1$ releases the lock but before it
aborts?  It will have read an incorrect value.  To solve the problem,
$T_2$ must either wait to read $x$ until $T_1$ has either committed or
aborted, or $T_2$ must also be aborted when $T_1$ aborts.  This is
called a {\it cascading abort}.  If transactions are allowed to read
uncommitted data, the implementation must be ready to deal with
cascading aborts.  One way to avoid them is called {\it strict}
two-phase locking:  transactions must hold all their locks until
commit or abort time.

Alternatively, operations can be allowed to interleave if they
commute.  This practice makes undo somewhat harder.  The old value of
the variable cannot just be restored.  For example:

\begin{tabbing}
\hspace{.8in}\= \kill
\>      $T_1$:  inc $x$ \\
\>      $T_2$:  inc $x$ \\
\end{tabbing}

If $T_1$ then aborts, the undo cannot just restore the old value of
$x$, since doing so would destroy $T_2$'s change.  Instead, the undo
must be a real inverse, and must also commute with the forward
operations on the data.

        
\end{document}
