\documentstyle[12pt,pocs-header]{article}
\Scribe{Dan Berkery}
\Lecturer{Bill Weihl}
\LectureNumber{17}
\LectureDate{14 November 1990}
\begin{document}
\MakeScribeTop

\section{Handouts}
\begin{itemize}
\item Handout 33: Final Course Schedule
\item Notes: Lecture 12
\item Notes: Lecture 13
\item Notes: Lecture 14
\item Notes: Lecture 15
\end{itemize}

\section{Today's Topic: Remote Procedure Calls}
Today's topic is remote procedure calls.  The basic idea behind
procedure calls is to try to emulate 
the semantics of local procedure calls as closely as possible.
Furthermore, the actual semantics which are implemented should be
carefully described, especially with respect to the differences
between local and remote calls.  There are two areas where the
behavior of remote procedure calls usually differs from the behavior
of local procedure calls; they are the semantics for references and
the semantics for failures.

\section{RPC}

Before discussing the differences between remote and local procedure
calls, the actual implementation of remote procedure calls should be
discussed.  There are three types of actions in a remote procedure
call:
\begin{itemize}
\item Binding: obtaining a remote procedure value which can be invoked
\item Marshaling/Unmarshaling: encoding/decoding the arguments for
and return values from the remote procedure call
\item Transport: getting the call and return values between the nodes
\end{itemize}

The point of RPC's is to allow a distributed system to be programmed in the
same way as a centralized system; if RPC's are implemented
effectively the same style can be used for procedure calls independent
of whether they are remote or local and decisions about which
resources reside locally and which do not can be changed easily.  

Before the methods of using and implementing remote procedure calls
are discussed, it is useful to develop some background information.
First some terminology should be mentioned;  the invoker of a remote
procedure call will be referred to as the client and the executor, for
lack of a better word, of the remote procedure call will be referred
to as the server. 
It is useful to compare the state in centralized systems and the state
in distributed systems.  Centralized systems have environments which
bind names to locations and memories which bind locations to values.
Distributed systems have name spaces which are analogous to the
centralized systems' environments and address spaces which are
analogous to centralized systems' memories.  A typical centralized
system has only one environment and memory, but a typical distributed
system may have multiple name spaces and usually has many separate
address spaces.  In addition the cost of referencing non local address
spaces in a distributed system may be several orders of magnitude
greater than the cost of addressing the local address space.


\subsection{Converting a Local Procedure to a Remote Procedure}
The goal of binding is to obtain a global procedure value which can be
invoked.  The question remains, however, ``What is a global procedure
value?''  In a centralized system a procedure value is essentially the
program counter for the first instruction in the procedure and,
depending on the semantics of the language, maybe a ``procedure''
environment.  In a distributed system more information is required;
typically a global procedure value includes the node address for the
node on which the corresponding local procedure will be invoked, the
address space which will be used on that node, and the program counter
or entry point in the given address space.

A simple method of converting a local procedure to a global one would
be to take the local node name, address space, and program counter and
package it up.  This method has the problem that the local procedure
must be changed to receive the call, unmarshal the arguments,
marshal the results and send the return.  A more effective
method is to construct a stub around the local procedure.  The stub
receives the call, unmarshals the arguments, calls the local
procedure, marshals the return value, and sends the return.  Thus, the
procedure which will be used globally, can be coded in the same manner
as one which would be used remotely.  In addition, a stub generator is
often a feature on systems which offer remote procedure calls. 

\subsection{Calling a remote procedure}
In order to call a remote procedure you need to know a name which
refers to it.  That name will be the name of a local procedure which
is a client side stub for the remote procedure.  The client side stub
marshals the arguments, sends the remote call, receives the reply, and
unmarshals the result.  Thus a remote procedure call should look just
like a local call on the client side.

\subsection{Binding}
How is a name found to refer to the global procedure?  It is a
question of naming; how things are named and when binding occurs.  In
SPEC there is a two level name space; procedures are referred to by the
module.procedure and the linker does the binding.  A
distributed system requires an analog, for clients need to import
procedure values and servers need to export procedure values.  A
typical mechanism for remote naming is to have a name server which
everyone knows how to contact and that provides a variety of naming
services.  For instance, it may provide the node name for a desired
server or an entire remote procedure value for a desired service.
Maintaining such a server is not a trivial task.  There are several
methods for implementing the binding.  The first is to bind
everything statically and distribute the analog of a static linker 
which binds global procedure values to names when programs are linked.
Obviously, this approach is not very flexible.  A second approach is 
to bind all names when the client starts up.  The problem with this
technique is that it cannot change instances and cannot bind to
multiple instances. A third approach is to bind names dynamically
depending on the arguments supplied.  A fourth approach is even more
flexible; it allows for binding values to be passed as arguments and
be returned as results.This final approach is needed to some degree in
order to construct name servers.  If individual programs are allowed
this degree of freedom, they can construct their own name servers(not always a great idea). 

\subsection{Marshaling}
There are two main problems in marshaling; the first is the meaning of
references and the second is the method of translation of data types
in heterogeneous systems.

The simplest way to deal with pointers is to disallow them.  A second
method is to convert them to copies; that is chase all of the pointers
until you get values and pass the actual structure.  If done cleverly,
this can maintain sharing in the argument structure. However, any
assignments to the copy do not change values in the client and local
semantics for aliasing are not preserved.  There is the option
of copying back on return, but this still does not preserve
call-by-reference 
semantics in concurrent environment.  Also, copying large structures
can be expensive.  A third method is to try to maintain the arguments
as pointers; this requires a sort of global reference frame.  There
are two types of implementation.  One is to make references
transparent, that is references have the same form whether local or
global.  Only one copy exists for any data so local reference
semantics are preserved.  This requires a message to be sent for every
reference which is not local.  This can be expensive.  The second
method is to use 
what are called opaque references. The idea is that the reference is
an opaque type which can only be interpreted in the address space from
which it was exported.  The way to get something done with it is to
take the opaque type and pass it back to the address space which
exported it.  An example is passing an opaque reference, much like a
file handle, back when a file is opened remotely.  To do anything with
the reference, like read a file or write a file, the reference must be
handed back to the file server.  The file system knows how to interpret
that opaque type.  In either case, if references are allowed, they can
result in an unexpected performance cost if the programmer expects all
procedures to be local.

The second issue in marshaling is the translation of data types in a
heterogeneous system.  Each node could have a different
representation for local data types.  The first and simplest method is
to define a standard message representation for each type.  The
problem with this strategy is that it requires two
conversions whether they are needed or not: if node A and node B both
have the same internal rep and node A wants to send a message to node
B, node A is required to convert to the standard format and node
B must decode the standard format rather than just shipping the bits.
A second strategy is ``receiver makes it right.''  The sender sends
his local representation tagged with a description and the receiver
must convert it to a 
representation which it can use.  The problem is that the receiver
must be prepared for all representations; if a new machine with new
representations is added each receiver must be changed (this may not
be feasible in a large system and is annoying even in a small system).
A third method is to allow a small set of possible representations to
be used.  The sender is allowed to choose a convenient representation
from the set of representations and the receiver only has to deal with
a small set of representations.  Certainly in the second method and
possibly in the third method, if the sender and receiver have the same
representations, no conversions will be required.  In the second and
third methods the receiver always pays the cost of translation, which
may be a drawback for centralized servers--because server overhead
should be minimized and clients are more likely than servers to have
cycles to burn. A fourth method is a negotiation protocol.  The client
requests certain formats and, since server computation is more
valuable, the server returns a message dictating the formats which are
convenient for the server while trying to accommodate the client's
requests. The problem with negotiation is that overhead is high if
only a few calls are going to be made.

\subsection{Transport}
The basic idea for RPC is that a call message is sent and a reply is
received. It will be assumed that a reliable and acknowledged message
protocol is available. The paper by Birrell and Nelson describes a
standard way to implement RPC that guarantees that a call message is
received and acted on at most once.  The basic idea is to keep a pool
of connections for each other node that has been accessed recently.
When a client or server wants to send a message, it checks to see if a
connection exists to that node and uses it if it does.  Connections are
kept around because setting them up is expensive.  Tailoring the
transport method to RPC would allow a reply to be the acknowledgment
to a call and the next call to be the acknowledgment to the previous
reply.  This works well if the time it takes a call to run is
typically less than the timeout interval for retransmission and the
interval between calls is typically less than the timeout interval for
retransmission.  Connections can be set up upon binding the global procedure
value or when the call is actually made.  If there has been a failure
of some sort than the connection must be reestablished.

\subsection{The Semantics of RPC}
The key issue in the semantics is what happens when things fail: when
machines crash, the network loses messages, or the network partitions
long enough so that retransmission times out.  An exception must be
signalled on the clients side so that the client knows something went
wrong.  The server, however, does not worry about failures.  The
real problem is that 
the client does not know the state of the system when he gets this
call failed exception.  The call may or may not have been executed.
Different RPC implementations guarantee different things.  Some
implement ``no guarantees'' meaning upon failure the client knows that
the call was executed zero or more times.  This lack of guarantees
makes reliable systems hard to build.  If there is to be any hope of
reliability in these systems, the server must provide a way for a
client to reconnect and 
find out the status of the server.  A better guarantee is an ``at most
once guarantee.''  On failure the client knows the call was executed
zero or one time or have partially executed. ``Zero or one'' semantics
would be better, because the client would be guaranteed that the call
was not executed or was executed fully.  To implement this strategy
requires some sort of distributed transactions mechanism.  

Sequencing is a second issue.  If you remove all files in a directory
remotely but get a failure and then reestablish the connection and
remotely move the only copy of your thesis there, you would be
upset if, after the move call executed, the failed remove call
suddenly popped up and executed.  It is preferred that when there is a
failure and a 
subsequent call is executed, that it be guaranteed that it is
impossible for that failed call to pop up and execute.  Basically, if
calls can be executed in an order other than the order in which they
were requested, serious problems can arise. In order to write
reliable applications, the programmer would like at most once
execution with ordering guaranteed to be preserved.
\end{document}
