\documentclass{article}

\usepackage{graphicx}
\usepackage{hyperref}

\title{CS3502 Project 1: Resource Management and Deadlocks}
\author{Kiana Sheibani}

\begin{document}
\maketitle
\newpage

\section{Introduction}

To demonstrate the basic principles of multi-threaded code, race conditions, and deadlock
avoidance, I ultimately decided to go with the model of a banking system, as that serves
as a simple and immediately applicable demonstration of the concept.

The code for this project is stored in the \texttt{project1} subdirectory of my repository
for this class: \url{https://github.com/tokinanpa/CS3502}.

\section{Phase 1: Basic Thread Operations}

\subsection{Implementation}

To start with, I simply needed some basic code of multiple threads trying to access and
manipulate an array of bank accounts. This code mostly looked similar to the example code
given in the instructions, except for the following differences:

\begin{enumerate}
        \item The currency amounts are stored as an integer number of cents instead of
        floating point values, because that's the sensible thing to do.
        \item Each thread is passed an argument struct that contains its own ID and a slot
        for it to pass back the total amount it deposited or withdrew.
\end{enumerate}

The program initializes an array of accounts (8) and of teller threads (128), then each
thread starts depositing or withdrawing a random amount of money up to \$1000 a certain
amount of times (100), while keeping a count of the total monetary influx that thread
introduced. After all threads have joined back up with the main program, the combined sum
from each thread (the \emph{expected total}) is compared with the sum of all account
balances (the \emph{actual total}). If no race conditions have occurred, these values
should be equal.

\subsection{Results}

\begin{figure}[h!]
  \centering
  \includegraphics[width=0.5\linewidth]{../phase1/output.png}
  \caption{The output log of the Phase 1 program.}
  \label{fig:result1}
\end{figure}

As expected, the program did indeed demonstrate that race conditions had occurred between
the threads, as can be seen in \figurename~\ref{fig:result1}. You may notice that the
transaction logs are somewhat garbled; this is because the logs are generated through
multiple \texttt{printf} calls, resulting in another example of a race condition. I did
not yet fix this bug because the repeated I/O calls helped delay the threads' execution
and make race conditions more likely.

\section{Phase 2: Resource Protection}

\subsection{Implementation}

This was a very small addition to the code for Phase 1, and just required adding a mutex
field \texttt{lock} to the account struct and initializing it at the beginning of the main
program. Each thread had to lock the mutex before the transaction and unlock it after,
which could be easily encapsulated into a subroutine \texttt{deposit} that takes a pointer
to the account and an amount.

I also fixed the bug shown earlier that garbled the log output by combining the log into
one \texttt{printf} call, which required a bit of rearranging in my code for printing currency
values but was only a minor complication.

\subsection{Results}

\begin{figure}[h!]
  \centering
  \includegraphics[width=0.5\linewidth]{../phase2/output.png}
  \caption{The output of Phase 2, showing total consistency.}
  \label{fig:result2}
\end{figure}

Even after increasing the number of transactions per teller from 100 to 1,000, the totals
always match up exactly.

Notably, there seems to be large blocks of transactions coming from singular threads. This
happened in the first program as well, most likely as an artifact of scheduling, but the
blocks are much larger here. This may be due to threads repeatedly unlocking and
re-locking a mutex, thus allowing them to instantly make another transaction.

\section{Phase 3: Deadlock Creation}

\subsection{Implementation}

It turns out that it's quite easy to make a program deadlock! I could have just had two
transactions competing for resources as in the example code, but I decided to implement a
more complex example. I kept the large array of threads, but changed the threads from
adding/subtracting random amounts to transferring money between two accounts. The accounts
to transfer between were passed as pointers to the threads using the struct-passing method
I outlined earlier, along with the amount to transfer (I arbitrarily picked \$10 for all threads).

I then initialized the same number of threads as accounts, and set each of them to start a
transfer from one account to the next. Thread 0 transfers from 0 to 1, thread 1 transfers
from 1 to 2, etc. This would in theory create a large circle of threads that would
deadlock and prevent any transaction from completing.

\subsection{Results}

\begin{figure}[h!]
  \centering
  \includegraphics[width=0.5\linewidth]{../phase3/output.png}
  \caption{The output of Phase 3 successfully generating a deadlock.}
  \label{fig:result3}
\end{figure}

The program hangs immediately once all threads have started, regardless of the number of
accounts or transfers I generate.

\section{Phase 4: Deadlock Resolution}

\subsection{Implementation}

To resolve the issue of these deadlocks in this code, I ultimately decided to go with a
lock ordering approach. This is the simplest and easiest to implement of the possible
solutions, though no program involving asynchonous execution is truly simple. The idea is
to always lock the resources in a consistent, absolute order (in this case, the order of
the account IDs).  This would prevent the threads from creating a cycle of lock
acquisitions, and thereby prevent any deadlocks from occurring.

\subsubsection{Challenges}

\begin{figure}[h!]
  \centering
  \includegraphics[width=0.5\linewidth]{../phase4/output_wrong.png}
  \caption{This log output shows account 0 being locked multiple times!}
  \label{fig:outputwrong}
\end{figure}

After running the code with different parameters, in particular in the case of there being
more transfers than accounts, I ran into a strange bug. It seemed like the mutexes were
being locked multiple times at once, which should be impossible. The final balances of
each account seemed to confirm that race conditions were occurring, as the total did not
sum to 0, as should be the case when no transfer is adding or removing money from the
bank.

\begin{figure}[h!]
  \centering
  \includegraphics[width=0.5\linewidth]{../phase4/mistake.png}
  \caption{The offending line of code (line 105).}
  \label{fig:mistake}
\end{figure}

It took me a long time to discover the true problem: transactions were receiving invalid
account IDs because one specific line of code accidentally left off a modulo operator
(\texttt{i \% NUM\_ACCOUNTS}) to constrain the accounts referenced to inside the array.
This meant that ``accounts'' were modified that did not actually exist, thus violating the
preservation of the total sum of balances. The log output issue was completely unrelated
and seems to solely be an artifact of race conditions in I/O operations. After fixing the
aforementioned line of code, the final account balances were fully consistent.

\subsection{Results}

\begin{figure}[h!]
  \centering
  \includegraphics[width=0.5\linewidth]{../phase4/output.png}
  \caption{The output log of Phase 4 showing consistent account balances.}
  \label{fig:result4}
\end{figure}

\figurename~\ref{fig:result4} shows that deadlocks have been completely avoided and that
no race conditions have occurred. This output was generated with 5 accounts and 20
transfers, meaning that every account has multiple threads attempting to modify its
balance without issue. The performance is also unchanged from previous phases, which may
not be the case for the other deadlock resolution methods (e.g. timed locks).

\end{document}
