Page Numbers: Yes X: 527 Y: 10.5" First Page: 1 Not-on-first-page
Margins: Top: 1" Bottom: .8"
Heading: Not-on-first-page
Lupine User’s Guide—Version 1.0
Lupine User’s Guide
An Introduction to Remote Procedure Calls in Cedar
Version 1.0
by
Bruce Nelson
and
Andrew Birrell
XEROX
Computer Science Laboratory
Palo Alto Research Center
July 8, 1982
[Indigo]<Cedar>Documentation>LupineUsersGuide.press
This document describes research software for Xerox internal use only.
Contents
1  Introduction  2
2  Translator Operation  3
3  Conversations, Encryption, and Secure Communication  6
4  Binding and Configuration  8
5  Crash Detection and Recovery  15
6  Parameter Passing  16
7  Interface and Parameter Restrictions  18
8  References  20
Appendix A: Public RPC Interfaces  21
Appendix B: Example Remote Interface and Stubs  25
1 Introduction
Lupine is the translator for Cedar’s [Cedar] remote procedure call mechanism. A thorough understanding of remote procedure call (RPC) principles is assumed throughout this guide—indeed, once-skeptical readers have assured us it is foolish to continue without adequate background. Nelson’s dissertation [RPC] provides more than the necessary foundation, as Lupine, not surprisingly, resembles that thesis’s Emissary mechanism closely (Lupine, however, includes no orphan algorithms). Be particularly conversant with chapters 1 and 2, and section 5.4.
Relationship to Courier
Xerox’s Office Products Division has its own RPC mechanism, Courier [CourierFS, CourierPS]. Courier is for use in standard Pilot [Pilot] environments and has its own Lupine-like translator, Diplomat [Diplomat]. The Courier system is supported by OPD and is released with Pilot; it is the product software RPC mechanism. Lupine, in contrast, is supported by CSL for use primarily within CSL and PARC. Lupine’s remote calls execute in both the Cedar and Pilot environments, are about ten times faster than Courier’s, and are significantly easier to program.
Operational Overview
Lupine reads a compiled Mesa interface and mechanically writes four Mesa source modules that implement the same interface remotely. For a given DEFINITIONS interface named Target, Lupine’s stub control interface and three stub modules are:
TargetRpcControl.mesa. This public definitions module contains declarations for the remote binding of Target.
TargetRpcClientImpl.mesa. This program module executes in hosts that want to import Target. TargetRpcClientImpl is the client stub module. It exports Target and TargetRpcControl.
TargetRpcBinderImpl.mesa. This optional program module is used to instantiate and import a dynamically varying number of remote Target interfaces. It exports TargetRpcControl but not Target. It depends on module TargetRpcClientImpl.
TargetRpcServerImpl.mesa. This program module executes in hosts that want to export Target. TargetRpcServerImpl is the server stub module. It imports Target and exports TargetRpcControl.
Appendix B contains an example of each of these generated modules. As you use this guide, refer to appendix B as necessary for concrete examples.
To use the stub modules compiled from the generated programs, clients include them in configurations on their client and server hosts. Aside from these configuration modifications, the only required program changes are two procedure calls to routines in TargetRpcControl. These binding calls, discussed in detail later, cause the serving machine to export Target and the client machine to import it. This scenario is slightly more complicated with secure calls or multiple instantiation, and much more complicated with dynamic crash recovery.
To establish bindings, make secure calls, and handle exceptions, Cedar RPC programmers use an RPCRuntime package interface called RPC. There is a parallel interface, MesaRPC, for standard Pilot programmers. Appendix A reproduces both of these interfaces.
Acknowledgments
Cedar’s RPC mechanism is the work of two people: Andrew Birrell designed and wrote the RPCRuntime package with its transport, binding, and secure communication subsystems; Bruce Nelson designed the stub format and wrote the Lupine translator and diagnostic tools. In addition, members of the Alpine and Voice projects—Mark Brown, Dan Swinehart, and Ed Taft, in particular—made numerous good suggestions about parameter marshaling and multiple interface instances. Dan was also a revealing alpha tester.
This guide is primarily Nelson’s venture, with Birrell contributing material on secure communication and remote binding.
2 Translator Operation
Installation
The Lupine translator runs in the Cedar environment (but a Tajo version would be trivial if needed). To install Lupine and all the necessary RPCRuntime underpinnings, use Bringover to retrieve the public components of [Indigo]<Cedar>Top>Lupine.df.
Translation
Lupine reads the BCD of a compiled interface and writes four output files. At present, Lupine is used from the Executive, although eventually it will be under the control of the Cedar system modeller. Establishing a command file to translate each remote interface, or package of interfaces, is recommended for now.
The basic procedure is this:
When your target interface is ready for remote use, compile it:
>Compile Target
Feed the resulting interface to Lupine:
>Lupine TranslateInterface["Target"]
Compile the four stub modules, in this order:

>Compile TargetRpcControl TargetRpcClientImpl,TargetRpcBinderImpl
TargetRpcServerImpl
Lupine records its translation progress in the Executive’s window and log. The source and object files for the stubs will be many times larger than the original interface, so be sure you have sufficient space for them.
To abort a translation just type control-DEL.
Commands and Syntax
Lupine can be invoked from the command line or used interactively from the keyboard. There is one real command, TranslateInterface, and a number of option-setting commands. They are presented here in procedural keyboard style. Consult a Lupine wizard before using a nondefault value for any option except TargetLanguage, InterMdsCalls, InlineDispatcherStubs, and LineLength.
<>TranslateInterface [fileName: STRING]
Lupine processes fileName.bcd, e.g., Target.bcd.
<>TargetLanguage [language: STRING ← "Cedar"]
TargetLanguage sets the programming language used to write the output files (do not confuse the TargetLanguage command with the use of Target as an example interface). The acceptable languages are Cedar and Mesa; Cedar is the default.
<>DefaultParameterPassing [method: STRING ← "VALUE"]
This command sets the default parameter passing method to be VAR, VALUE, RESULT, or HANDLE. (These are explained in a later section on Parameter Passing Methods.) VALUE is the default, and take heed that this is different from Mesa’s default semantics for address-containing values.
<>InterMdsCalls [yes: BOOLEANFALSE]
When InterMdsCalls is true, the importers and exporters of Target must operate within the same host (the remote binder insures this), but can execute in different MDSs. This tighter binding yields much faster parameter marshaling for address-containing parameters. Note, however, that intraMDS addresses such as POINTERs and STRINGs are necessarily illegal in interMDS calls, and Lupine issues warnings if they are used. The default is false.
<>InlineDispatcherStubs [yes: BOOLEANTRUE]
Lupine usually writes a single large procedure in the server stub program. For large or complicated remote interfaces, however, the compiler can overflow when it compiles this huge dispatcher procedure. If this happens, setting InlineDispatcherStubs to false may cure the overflow problem by writing many small procedures instead. If not, there two distasteful alternatives: either simplify the troublesome interface, or split it into two smaller interfaces. The default is true.
<>FreeServerArguments [yes: BOOLEANTRUE]
When FreeServerArguments is true, Lupine automatically deallocates any noncollected storage that it allocates to unmarshal arguments in the server stubs. When false, it does not deallocate this storage, presumably because the underlying server implementation is going to assume responsibility for deallocation itself. This unSAFE feature is only for Mesa—not Cedar—RPC interfaces that use this contorted allocation strategy. It applies only to arguments: storage for results is never deallocated in either the client or the server. The default is true.
<>DynamicHeapNews [heap, mds: INTEGER ← 50, 50]
A Mesa remote interface can have parameters with a dynamic number of noncollected objects (i.e., objects NEWed from the heap, such as a descriptor of strings or a sequence of pointers). Lupine imposes a translation-time upper bound on the total number of these dynamic objects allowed in the argument list—not result list—of a single call. The default is 50 heap (long pointer) objects and 50 mds (short pointer) objects.
<>LineLength [length: INTEGER ← 82]
LineLength specifies the approximate maximum length of source lines in the generated output files. The default, 82, is a good choice for Viewers and Tajo.
There are additional debugging commands for wizards only. Type ? at any point for a list of possible commands or responses.
Command Files
The following example illustrates option-setting and translation in command files:
>Lupine DefaultParameterPassing["VAR"] InlineDispatcherStubs[FALSE]
LineLength[60] TranslateInterface["
Target"]
Lupine Errors
Fortunately, because Lupine’s input is compiler output, few translation errors are possible. Most errors are caused by parameter passing restrictions. Lupine reports errors both in the typescript and in the output file itself. Error and warning messages in the typescript include the output file name and character position of the error, and messages in the output file are preceded with "#####" to make them easy to spot. Many errors cause an ERROR statement to be inserted in the output file. This prevents a bogus stub from executing when the result would be chaos. If you can’t understand a particular error message, see a Lupine wizard.
As mentioned above, if a set of stubs is too big to compile, the InlineDispatcherStubs option may remedy the problem.
Compiler Warnings
The compiler will occasionally issue warning messages about referenced but unused DIRECTORY items as it compiles stub programs. Lupine is a mechanical translator, and eliminating the spurious warnings would be difficult. Live with them.
3 Conversations, Encryption, and Secure Communication
Lupine and the RPC runtime provide facilities for making secure remote calls. Such calls are encrypted using the DES encryption algorithm [DES]. The protocols used provide secure two-way authentication without any passwords being passed in the clear [Needham-Schroeder]. Secure RPC calls prevent an intruder from seeing the procedure names, parameters, or results of the calls; from modifying calls in transit; from replaying previously valid calls; from observing passwords (encryption keys) in transit; from observing the cipher text of known (or chosen) plain text.
Secure RPC communication is based on the concept of a conversation. A conversation takes place between two authenticated Grapevine individuals, known as the originator and the responder of the conversation.
RPC.Principal: TYPE = Rope.ROPE;
RPC.EncryptionKey: TYPE = BodyDefs.Password;
RPC.Conversation:TYPE = . . . ;
RPC.SecurityLevel = {
none,
authOnly,
ECB,
CBC,
CBCCheck};
RPC.GenerateConversation: PROC RETURNS[Conversation];
RPC.StartConversation: PROC [
caller: Principal,
key: EncryptionKey,
callee: Principal,
level: SecurityLevel[authOnly..CBCCheck] ]
RETURNS[Conversation];
RPC.EndConversation: PROC [conversation: Conversation];
RPC.GetCaller: PROC [conversation: Conversation] RETURNS [Principal];
RPC.GetLevel: PROC [conversation: Conversation] RETURNS [SecurityLevel];
RPC.ConversationID: TYPE [3];
RPC.GetConversationID: PROC[conversation: Conversation]
RETURNS [id: ConversationID];
RPC.AuthenticateFailed: ERROR [why: RPC.AuthenticateFailure];
A conversation is created by the originator calling RPC.StartConversation and is destroyed by the originator calling RPC.EndConversation. The originator provides his own name and password and the name of the responder. StartConversation communicates with Grapevine to verify the validity of these arguments; at this time there is no communication with the responder. See below for a description of the SecurityLevel of a conversation. At present, the communication with Grapevine does not use secure protocols.
Having created the conversation, the originator may now make secure calls to the responder by passing the Conversation as the first argument of any remote call to a procedure in an interface that is exported by the responder. (The same conversation may be used to make calls in multiple interfaces.) The RPC runtime guarantees that the call can be understood only inside a host containing the authenticated responder and that the call will be received by the responder at most once. The responder can determine the authenticated identity of the originator by calling RPC.GetCaller when he receives a call having the Conversation as its first argument. Every conversation is uniquely identified for all time by an identifier that either party may obtain by calling RPC.GetConversationID. Calling RPC.StartConversation may raise the exception AuthenticateFailed, with the following values for its argument:
communicationsUnable to contact Grapevine.
badCallerThe originator’s name is not a Grapevine individual.
badKeyThe originator’s password was incorrect.
badCalleeThe responder’s name is not a Grapevine individual.
The procedure GenerateConversation may be called to obtain a Conversation to be used solely for calls within a single machine; such a Conversation must not be used for remote calls.
The SecurityLevel passed to StartConversation indicates how much security the originator wants for this conversation. The SecurityLevel of any conversation may be obtained by calling GetLevel. The meanings of the conversation levels are as follows. none indicates that the conversation was created by calling GenerateConversation, and that no authentication or encryption has been employed. authOnly will perform the authentication, but calls are not encrypted, and modification, manufacture or replay of calls or results is not detected. ECB uses the ECB mode of DES [DES], which protects calls and results from eavesdropping (and makes modifying, manufacturing or replaying calls or results somewhat more difficult). ECB mode does not hide patterns of repeated data within or between calls. CBC uses the CBC mode of DES [DES],, which behaves like ECB except that it hides repeated data patterns. CBCCheck is like CBC, with the addition of a checksum on each call and result; this prevents any eavesdropping, modification, manufacture or replay of a call or result. CBCCheck should normally be used for secure communication, and authOnly should normally be used for applications where only authantication is desired; the other values of SecurityLevel are advisable only where hardware or performance constraints dictate. At present the DES algorithm is replaced by a trivial exclusive-or algorithm, because of the absence of encryption hardware.
4 Binding and Configuration
Lupine does not perform remote binding by using remote versions of the modeller or C/Mesa binder [Mesa]. Instead, the modeller or binder are used to bind—locally—to an instance of TargetRpcControl. A remote interface, say Target, is then bound remotely by calling binding procedures in the TargetRpcControl interface. These procedures use the RPCRuntime, in cooperation with Grapevine [Grapevine], to perform remote binding. This is elaborated below, after a discussion of remote interface naming.
Interface Names, Instances, and Versions
Naming remote interfaces is much more complicated than naming local-machine interfaces because remote interfaces must be uniquely identifiable in a distributed environment. Furthermore, ability to control the degree of typechecking is required for widespread systems such as Laurel (see sections 4.1.3.1 and 4.1.3.4 in Nelson’s thesis [RPC]). Lupine addresses this problem with InterfaceNames, defined below. (ShortROPEs are discussed in the Parameter Passing section.)
RPC.ShortROPE: TYPE = Rope.ROPE;
RPC.VersionRange: TYPE = RECORD [first, last: CARDINAL];
RPC.matchAllVersions: VersionRange = . . .;
RPC.InterfaceName: RECORD [
type: ShortROPENIL,
instance: ShortROPENIL,
version: VersionRangematchAllVersions ];
RPC.defaultInterfaceName: InterfaceName = [];
InterfaceName.type
The type in an interface name specifies which interface is being exported or imported. This is not a precisely defined concept, since it is up to the implementors of interfaces to separate their interfaces into types, but it is intended to correspond to a single Mesa definitions module. Only the implementor can decide at what stage in the incremental development of an interface it should be considered as a new type. In the distributed environment, there may be multiple exporters of a given type of interface. For example, if the interface is for access to a printer, each printer would export the same type of interface. These multiple interfaces are distinguished by their InterfaceName.instance, described below.
The type of an interface may be a Grapevine R-Name, or an arbitrary string chosen by the implementor. One approach is to use the corresponding definitions module name as the type. The effects of using these forms of type are described below.
InterfaceName.instance
The instance in an interface name specifies which particular one of the (potentially) many instances of this interface is being named. For example, if the interface is for access to a printer and is exported by each printer, each printer would export the same interface type but a different interface instance.
The instance of an interface may be a Grapevine R-Name, or a host name (for example, "Ivy"), or a Pup network address of the exporting host (for example, "3#17#"). The effects of using these forms of instance are described below.
Multiple remotely exported interfaces may have the same instance, provided that they each have a different type and they are exported from the same host.
InterfaceName.version
The version in an interface name may be used to control the extent to which the remote binder will check compatibility of versions between the exporter and importer. If the exporter of a remote interface specifies [a,b] as the InterfaceName.version and the importer specifies [c,d], then the remote binding will be allowed to succeed only if the ranges [a..b] and [c..d] have a non-empty intersection. However, RPC.matchAllVersions is assumed to intersect with every version range. That is, if RPC.matchAllVersions is specified by either the importer or the exporter, then the remote binding will not fail because of version mis-match.
By using this facility, the implementors are asserting that they support, at a bit-wise compatible level, multiple versions of an interface. This freedom is in marked contrast to the Mesa binder (and loader), which insist on identity of versions for binding to succeed. This extra freedom seems necessary in the distributed environment of RPC, but may well be a fertile source of bugs.
To remain upward compatible with previous versions of an interface, you may add procedures (or signals or errors) only at the end of the interface. You must not make any significant changes to any types (for example, changing the order of fields in a record or of parameters to a procedure is significant, as is adding fields or parameters). You may change the names of things (like procedures, fields, parameters). You may also use this version field to indicate semantic changes that do not involve changes to the definitions file.
Exporting
For interface Target, calling TargetRpcControl.ExportInterface causes Target (as specified by interfaceName) to be exported for immediate remote use. Any attempts to import Target before ExportInterface completes, or after UnexportInterface completes, will raise an exception (see the description of importing, below). These two routines must be called in the strict sequence ExportInterface, UnexportInterface, ExportInterface, .... Calling UnexportInterface has no effect on calls in progress.
TargetRpcControl.ExportInterface: PROCEDURE [
interfaceName: InterfaceNamedefaultInterfaceName,
user: RPC.Principal,
password: RPC.EncryptionKey,
parameterStorage: ZonesstandardZones ];
TargetRpcControl.UnexportInterface: PROCEDURE;
RPC.ExportFailed: ERROR [why: RPC.ExportFailure];
The precise behavior of ExportInterface depends on the value of interfaceName. If the interfaceName.instance is a Grapevine R-Name, the Grapevine registration service is consulted to ensure that the instance is an individual whose connect-site is a Pup net address for the exporting machine. If necessary, the database is updated, using the given user and password as credentials. If the instance is not an R-Name (that is, it does not contain a dot), it should be either a Pup Name Lookup Server name that maps to the exporting machine, or a Pup net address constant for the exporting machine. If the interfaceName.type is an R-Name, the Grapevine database is consulted (and updated if necessary) to ensure that the type is a group, one of whose members is the interfaceName.instance. At present, these actions on an interfaceName.type which is an R-Name are not implemented; every type is treated as just a string.
If you call ExportInterface and default the interfaceName.type, Lupine will use for the type a combination of the interface name (for example, "Target") and the compiler’s version stamp for the interface. The default type is thus a unique name for the remote interface, and this uniqueness permits the default version to be matchAllVersions — of which there can be just one.
Calling ExportInterface with a defaulted interfaceName.instance is not yet supported; exporters and importers must give an explicit instance each time. For simple experiments, it is convenient to use your own Grapevine R-Name as the instance, and your own credentials as parameters to ExportInterface. For example:
myInterface: InterfaceName = [instance: "Nelson.pa"]; -- Use default type & version.
TargetRpcControl.ExportInterface [
interfaceName: myInterface,
user: "Nelson.pa",
password: Rpc.MakeKey[ "Nelson’s password"] ];
If an attempt to export a remote interface fails, the exception ExportFailed is raised. No monitors are locked when this signal is raised, so further calls into the RPC runtime may be made from catch-phrases. The parameter of the signal is one of the following:
communicationsExporting failed because of inability to contact Grapevine or a Pup Name Lookup server.
badTypeThe interfaceName.type was illegal (a Grapevine R-Name, but not a group).
badInstanceThe interfaceName.instance was illegal (NIL, not a Grapevine R-Name, not an individual, not a Name Lookup server name for this host, not a net address constant for this host).
badVersionThe interfaceName.version was illegal (upper bound less than lower bound).
tooManyToo many current exports for local tables — consult a Lupine wizard.
badCredentialsThe credentials were inadequate for updating the Grapevine database.
Importing
Remote importing of an interface Target may be caused by calling TargetRPCControl.ImportInterface or TargetRPCControl.ImportNewInterface, as described below. In either case, an interfaceName is provided, which is interpreted as follows.
If interfaceName.instance is not NIL, then it must be: a Grapevine R-Name that is an individual whose connect-site is a Pup net address, a Pup Name Lookup server host name, or a Pup net address constant. Each of these maps to a net address of a machine, which is assumed to be the machine that has previously exported the desired interface. If that machine is running the Cedar RPC package and has exported an interface whose InterfaceName has the same type and instance, and whose version is compatible with the importer’s interfaceName.version, then the import succeeds and the importer’s stub is now bound to the exporter’s stub.
If interfaceName.instance is NIL, then the remote binder will attempt to find any exported interface whose type and version match those given in interfaceName. If the type is a Grapevine R-Name, then it should specify a Grapevine group. The binder enumerates the members of that group, obtains the network addresses that are the connect-sites of the members, and tries to bind to them in turn until one succeeds. The order of these binding attempts is undefined, but is closely related to hop-count and responsiveness. The algorithm usually chooses the nearest instance of the type that is operational. If the type is a Pup Name Lookup server name string, then this algorithm is performed with the network addresses that are the value of that name. Importing with interfaceName.instance = NIL is not yet implemented.
If an attempt to import a remote interface fails, the ERROR ImportFailed is raised. No monitors are locked when this signal is raised, so further calls into the RPC runtime may be made from catch-phrases. The parameter of the signal is one of the following:
communicationsImporting failed because of inability to contact one of: Grapevine, a Pup Name Lookup server, the exporting host.
badTypeThe interfaceName.type was illegal (interfaceName.instance is NIL and type is not a Grapevine R-Name of a group, and not a Name Lookup server name).
badInstanceThe interfaceName.instance was illegal (not a Grapevine R-Name, not an individual, not a Name Lookup server name, not a net address constant).
badVersionThe interfaceName.version was illegal (upper bound less than lower bound).
wrongVersionThe exporter and importer version ranges don’t overlap.
unboundNo currently running host has exported the appropriate type and instance.
stubProtocolThe exporter and importer are using stub modules produced by incompatible verisons of Lupine.
Importing One Remote Instance
For interface Target, calling TargetRpcControl.ImportInterface causes Target (as specified by interfaceName) to be remotely imported for immediate use. In the C/Mesa sense, Target was bound when TargetRpcClientImpl, which exports Target locally, was bound. But remote binding is not complete until ImportInterface is called. As a consequence, any attempts to use operations in Target before ImportInterface completes, or after UnimportInterface completes, will fail unrecoverably. Calling UnimportInterface has no effect on calls in progress. These two routines must be called in the strict sequence ImportInterface, UnimportInterface, ImportInterface, .... Note that ImportInterface[someInterface] will raise the exception RPC.ImportFailed[unbound] unless some host has previously called ExportInterface[someInterface].
TargetRpcControl.ImportInterface: PROCEDURE [
interfaceName: InterfaceNamedefaultInterfaceName,
parameterStorage: ZonesstandardZones ];
TargetRpcControl.UnimportInterface: PROCEDURE;
RPC.ImportFailed: ERROR [why: {. . .}];
Importing Multiple Remote Instances
For interface Target, calling TargetRpcControl.ImportNewInterface causes a new instance of Target (as specified by interfaceName) to be created and remotely imported for immediate use. Typically, each call of ImportNewInterface will specify a different value for interfaceName.instance, thus binding to a different, distributed instance of the Target interface. The operations in this new instance must be accessed through the interfaceRecord that ImportNewInterface returns, not through Target itself, as in the static importing case discussed above. (The Target interface is useless to RPC programmers using multiple instances and should not even be referenced.) For example, if Target contains procedure WritePages, a call to WritePages must be written interfaceRecord.WritePages[. . .], not Target.WritePages[. . .]. Any attempts to use operations in interfaceRecord before ImportNewInterface completes, or after UnimportNewInterface completes, will fail unrecoverably. Calling UnimportNewInterface has no effect on calls in progress. UnimportNewInterface is available only for the Mesa target environment. In Cedar, discarded remote interfaces are garbage collected. Note that ImportNewInterface[someInterface] will fail unless some host has previously called ExportInterface[someInterface].
TargetRpcControl.InterfaceRecord: TYPE = REF RECORD [. . .];
TargetRpcControl.ImportNewInterface: PROCEDURE [
interfaceName: InterfaceNamedefaultInterfaceName,
parameterStorage: ZonesstandardZones ]
RETURNS [interfaceRecord: InterfaceRecord];
TargetRpcControl.UnimportNewInterface: PROCEDURE [
interfaceRecord: InterfaceRecord ]
RETURNS [nilInterface: InterfaceRecordNIL]; -- Only for Mesa, not Cedar.
RPC.ImportFailed: ERROR [why: {. . .}];
Importing Multiple Local Instances
When a distributed program deals with multiple remote instances, it is often true that one (or more) of the instances will actually reside on the local host. In this case, performing remote calls to the local instance can have an undesirable performance impact. RPC programmers who want to optimize "remote" calls to local dynamic instances can do so in a simple and transparent fashion:
Copy and rename TargetRpcBinderImpl to be TargetLocalBinderImpl.
Edit TargetLocalBinderImpl, removing all the RPCLupine calls and changing ImportNewInterface to NEW TargetImpl and not TargetRpcClientImpl.
TargetRpcControl.ImportNewInterface—as implemented by TargetLocalBinderImpl—now returns an interfaceRecord for a local instance, not a remote one. The binding and communication optimizations are transparent to users of TargetRpcControl and interfaceRecord. See a Lupine wizard for more information.
Parameter Storage Zones
To unmarshal certain parameters, Lupine’s stubs must allocate private storage. This storage is taken from zones that RPC programmers can specify at binding time. For simple interfaces a set of standard zones is adequate, and thus the binding operations have default standardZones (below). For production interfaces that use address-containing (AC) parameters frequently, however, establishing special-purpose zones can increase performance significantly. If a remote interface uses a large number of small AC objects (except for ropes, which get special treatment from the Rope package), or it uses any large AC objects, consult a Lupine wizard.
RPC.Zones: RECORD [
gc: ZONENIL,
heap: UNCOUNTED ZONENIL,
mds: MDSZoneNIL ];
RPC.standardZones: Zones = [];
Module Dependencies
The compile-time relationships of Target modules, Lupine’s stub modules, and the RPCRuntime packages are shown below. If module B is to the right of module A, then B depends on A.
RPC or MesaRPC
RPCLupine
LupineRuntime
RPCRuntime package
Target
Target’s clients
TargetImpl
TargetRpcControl
Target’s remote binding modules
LupineRuntime (repeated from above)
TargetRpcClientImpl
TargetRpcBinderImpl
TargetRpcServerImpl
DF Files for Remote Interfaces
As a general rule, Lupine.df exports all the Lupine, LupineRuntime, RPCRuntime, and Cedar runtime components needed by remote programs. Thus DF files describing remote applications should say [Exports] Imports Lupine.df to get all required components, although tailoring with a USING list may be worthwhile.
Configurations Using Remote Interfaces
Remote calls typically take place between independent hosts, and thus, for a given Target interface, Target’s client and implementation modules, along with the necessary stubs, must be split between the cooperating hosts. A simple division of the Target service into remote TargetClient and TargetServer C/Mesa configurations is illustrated below. Constructing corresponding Cedar models would be similar.
TargetClient: CONFIGURATION
IMPORTS
-- For Lupine-generated modules:
Atom, ConvertUnsafe, Heap, Inline, LupineRuntime, Rope, RopeInline,
RPCLupine, SafeStorage, UnsafeStorage,
-- For ClientRpcImportImpl and TargetClientImpl:
. . .
CONTROL ClientRpcImportImpl, TargetClientImpl
= BEGIN
TargetRpcClientImpl; -- EXPORTS Target, TargetRpcControl.
TargetRpcBinderImpl; -- Optional, for multiple instances via ImportNewInterface.
ClientRpcImportImpl; -- IMPORTS TargetRpcControl, calls Import(New)Interface.
TargetClientImpl; -- IMPORTS Target (one instance) or uses interfaceRecord (for multiple).
END;
TargetServer: CONFIGURATION
IMPORTS
-- For Lupine-generated modules:
Atom, ConvertUnsafe, Heap, Inline, LupineRuntime, Rope, RopeInline,
RPCLupine, SafeStorage, UnsafeStorage,
-- For ServerRpcExportImpl and TargetImpl:
. . .
CONTROL ServerRpcExportImpl
= BEGIN
TargetRpcServerImpl; -- IMPORTS Target, EXPORTS TargetRpcControl.
ServerRpcExportImpl; -- IMPORTS TargetRpcControl, calls ExportInterface.
TargetImpl; -- EXPORTS Target.
END;
The purpose of the user-written ClientRpcImportImpl and ServerRpcExportImpl modules is to import and export the remote Target interface by making calls on TargetRpcControl’s Import(New)Interface and ExportInterface routines. These trivial binding operations could just as well be placed in TargetClientImpl and TargetImpl, or combined with the binding operations of other remote interfaces into one master binding module. Note that these configurations do not include the RPC runtime package, RPCRuntime.bcd. This package should normally be loaded separately, because it is essential that only one copy of this package is loaded into any machine. Check with current Cedar release documentation to see whether RPCRuntime.bcd is included in the Cedar boot file.
It is sometimes advantageous to have both client and server configurations loaded into one host, as for single-machine debugging. Such configurations must be specified cautiously, however, so that remote imports and exports are not misconnected locally, short circuiting all remote calls. The easiest way to do this is to use nested configurations, as in the following example.
TargetTestCombined: CONFIGURATION
IMPORTS
-- For Lupine:
Atom, ConvertUnsafe, Heap, Inline, LupineRuntime, Rope, RopeInline,
RPCLupine, SafeStorage, UnsafeStorage,
-- For TargetImpl, ClientImpl:
. . .
CONTROL TargetServer, TargetClient
= BEGIN
TargetClient;
TargetServer;
END;
These TargetClient and TargetServer examples are simple; more elaborate configurations will be commonly required. See appendix A of the Diplomat document [Diplomat] for further, more complicated, examples, or consult a Lupine wizard.
5 Crash Detection and Recovery
A general discussion of crashes and crash recovery in the context of RPC is outside the scope of this guide. The main reason is insufficient experience with distributed systems: there is no widespread agreement on an RPC-level crash recovery methodology, or, indeed, if such a methodology is even needed or possible at this level. In currently operational distributed systems, those with the best crash behavior appear to use highly application-specific techniques. See Nelson’s thesis [RPC] for more discussion (sections 4.1.1, 4.2.3, 5.1, 7.2).
RPC.CallFailed: SIGNAL [why: RPC.CallFailure];
Lupine’s RPCRuntime uses the RPC.CallFailed exception to report crashes and other remote call execution problems. This exception can be reported by any invocation of a remote procedure, or on any signal of a remote signal or error. RPC programmers interested in continuous service must therefore be prepared to catch CallFailed on each remote operation. Recovery actions usually consist of restoring some invariants before trying to rebind to another instance of the remote interface. No monitors are locked when RPC.CallFailed is raised, so further calls into the RPC runtime may be made from catch-phrases. The parameter of the signal is one of the following:
timeoutThe other participant in the call is not responding to attempts to communicate. The remote call may or may not have taken place. Note that this is not raised merely because the remote call takes a long time to execute. The RPC runtime places no limit on the execution time of a remote call, provided the server still responds to probes. A client can terminate a call that is taking too long by using the Abort mechanism of the Mesa process machinery. RPC.CallFailed may be resumed iff its parameter is timeout.
unboundThe server has un3exported this interface, or has been restarted, since the interface was imported. The call did not take place.
busyAfter repeated attempts, the server is too busy to accept the call. The call did not take place.
protocolThe caller and server runtimes disagree about the communication protocol; consult a Lupine wizard.
stubProtocolThe caller and server stubs disagree about the communication protocol; consult a Lupine wizard.
Note for experts: In servers, the RPCRuntime is prepared to catch CallFailed itself and abort the incoming call that caused it. Thus if CallFailed is signalled out of a remote exception or a callback (procedure parameter), and the exception or procedure was invoked by a remote call executing in a server, the RPC programmer’s server code does not have to catch CallFailure. The RPCRuntime will automatically clean up and ignore the original call, if desired.
6 Parameter Passing
Semantics
For parameters that do not contain addresses, Lupine’s parameter passing semantics are exactly those of Cedar and Mesa: call-by-value. For address-containing (AC) parameters, however, Lupine has four passing semantics: for arguments, VAR, VALUE, RESULT, and HANDLE; for results, just VALUE. Each of these is a copy semantics; Lupine does not support reference semantics (call-by-reference) between disjoint address spaces. Here is a brief description of each; the next secion has examples:
VALUE. Value semantics cause the actual argument to be evaluated and assigned (copied) to the formal argument at the time of invocation. For AC types used as RPC parameters, the referent and not the address is used. For example, if p is a REF INT, then passing p by VALUE uses the integer p↑, not the pointer p (HANDLE passing would use just p). NIL semantics are preserved—e.g., if p is NIL, then NIL is passed. This dereferencing behavior also applies to POINTERs (same as REFs), DESCRIPTORs (the underlying array is copied), LISTs (the individual elements are copied to a new list), and strings (ropes, atoms, and strings).
RESULT. Result semantics cause the value of the formal AC argument to be assigned to the actual argument at the completion of the call. Arguments are dereferenced as described above. The initial value of the actual argument is unspecified.
VAR. VAR means VALUE-and-RESULT: the actual argument is evaluated and assigned to the formal argument at the start of the call, and the final value of the formal argument is assigned back to the actual argument when the call finishes. In the absence of aliasing, VAR semantics are identical to reference semantics.
HANDLE. An AC argument passed as a handle is treated as a numeric quantity, not an address. There is no interpretation or dereferencing of the address itself. Lupine does not consider a handle type to be an AC type. The compiler, however, does, and this can cause serious safety breeches for REF-containing types used as RPC handles. Basically, Cedar RPC programmers cannot use REF-containing handles; see a Lupine wizard.
Syntax
The specification of parameter passing semantics is somewhat awkward because Mesa and Cedar support only call-by-value semantics. There are two methods:
Explicit. To explicitly declare a passing method, RPC programmers include VAR, VALUE, RESULT, or HANDLE as a prefix to a parameter’s type name. For example,
Buffer: TYPE = DESCRIPTOR FOR ARRAY OF DiskPage;
VALUEBuffer: TYPE = Buffer;
RESULTBuffer: TYPE = Buffer;
VARBuffer: TYPE = Buffer;
ReadPages: PROCEDURE [..., inputPages: RESULTBuffer];
WritePages: PROCEDURE [..., outputPages: VALUEBuffer];
XORPages: PROCEDURE [previousPages: VARBuffer, nextPage: VALUEBuffer];
Implicit. To implicitly establish a passing method RPC programmers use the DefaultPassingMethod option at translation time. Such blanket declarations are not recommended, and you should see a Lupine wizard before using DefaultPassingMethod.
Applicability
The following table indicates which passing methods are applicable to each type (a blank entry means No). Value semantics is the default in all cases. Further restrictions are listed below.
The types below can be passed byVARVALUERESULTHANDLE
INT, BOOL, CHAR, REAL, ...Yes
Subrange, EnumerationYes
RECORD, variant RECORDYes
ARRAY, SEQUENCEYes
OPAQUE (with assignment)Yes
ANY, OPAQUE (without assignment)No
CONDITION, MONITORLOCKNo
ZONENoYes
PROCEDURE, SIGNAL, ERRORNot yetYes
REF, POINTER, DESCRIPTORYes1YesYes1Yes
RELATIVE POINTER2Yes
LIST, ROPE, ATOM, ShortROPE, ShortATOM3YesYes
STRING, ShortSTRING3YesYesYesYes
TEXT, STRINGBODYYes
1. These AC types can be called by VAR and RESULT, but their referents may not in turn contain any AC types. Restated, VAR and RESULT are only one level deep.
2. Lupine does not consider relative pointers to be AC, and always treats them as numeric values.
3. ShortROPE, ShortSTRING, and ShortATOM are string types that should be used by RPC programmers only when a string’s length has a guaranteed limit. Grapevine’s RNames are a primary example because they will frequently be used in remote calls to specify users, principals, servers, and so forth. The use of short strings is always optional; when used, Lupine can usually optimize stubs by guaranteeing that a call always fits in one packet. The short string length limit, defined by RPC.maxShortStringLength, is currently 64. The other properties of these types are identical to those of their longer brothers.
Guarantees
Lupine’s typechecking is no stronger than the compiler’s. In generating code, Lupine assumes that the value of a parameter is always a legal value for a parameter of that type—there is no special checking. If a client programmer passes a REF which has been loopholed to an invalid address, or passes an out-of-bounds enumerated or subrange value, the results are unspecified. For example, if Lupine must dereference the bogus REF during marshaling, either an address fault will occur or an equally bogus referent will be handed to an unsuspecting server. For an invalid enumerated or subrange value, a fault will occur only if the stub modules were compiled with bounds checking enabled.
The upshot is that RPC programmers must be just as suspicious of invalid parameters as they would be in the absence of RPC. Furthermore, programmers of servers must be especially careful because RPC client code need not be generated by Lupine, or even written in Mesa.
7 Interface and Parameter Restrictions
Lupine tries hard to make local and remote procedure semantics identical, and as a result there are very few restrictions. Here are the details.
Interface Remarks and Restrictions
PROGRAMs, PROCESSes, and PORTs. Lupine supports only remote procedures, signals, and errors. Remote programs, processes, and ports are not permitted.
Interface variables. Lupine does not support global variables in interfaces, including procedures and other items accessed through POINTER TO FRAME.
INLINE procedures. Lupine ignores inline procedures in remote interfaces, since inline procedure bodies appear in the interface itself and are compiled directly into clients.
Monitors. A procedure’s ENTRY attribute is invisible in interfaces, and thus stub procedures never have the ENTRY attribute. This is exactly the desired behavior, for the actual implementation is the only module that may need to be a monitor.
Multiple module names. Lupine does not support multiple module names for remote interfaces (e.g., A, B, C: DEFINITIONS).
Default parameters and dynamic interface instances. Dynamic interface instantiation via ImportNewInterface uses an explicit interface record declared in TargetRpcControl. Because the parameter declarations in this record do not duplicate any explicit defaults declared with the formal parameters in Target (a symbol table shortcoming), explicit parameter defaulting cannot occur. (Implicit default values from the parameter’s type declaration, however, continue to work properly.) This is in contrast to remote calls through a single Target instance, where the defaults, if any, are available.
Identifier conflicts. While unlikely, it’s possible that an internal stub-module identifier used by Lupine (e.g., temporary variable) will conflict with an external identifier used by a remote interface (e.g., procedure or parameter name). The compiler will discover and complain about such conflicts, which should be reported to Lupine’s implementors straightaway. The short-term repair, however, is renaming the conflicting identifiers in the remote interface.
Parameter Marshaling Restrictions
Lists, trees, and graphs. Lupine does not automatically marshal these recursive data structures when they are constructed from explicit REFs or POINTERs. Only noncyclic LIST structures are supported.
Callback parameters. Procedure, signal, and error parameters are on the verge of working. If you need them, tell a Lupine wizard, who might finish the job just for you.
Computed sequences and variant records. Lupine cannot marshal any computed sequences, nor can Lupine marshal computed or overlaid variant records with AC components.
Sequences inside variant records. Lupine cannot marshal a sequence contained directly within a variant record. However, Lupine happily accepts references to sequences Thus the first line below is illegal, but the second is acceptable:
RECORD [SELECT color: * FROM red => [SEQUENCE . . . OF Intensity]];
RECORD [SELECT color: * FROM red => [REF RECORD[SEQUENCE . . . OF Intensity]]];
Anonymous record components and single-component records. Lupine cannot cope with anonymous record components. (Anonymous parameters, however, are fine, except for signal and error arguments.) Therefore unnamed, single- or multi-component, address-containing (AC) records will cause a translation error. To recover, just give the components names.
Default values for variant records. Sometimes, to marshal a variant record, Lupine must NEW storage for it. Safety requires that the new record have an initial tag value, but Lupine is not in a position to choose the correct one. An RPC programmer must therefore specify a default value for the record in the record’s declaration, even if Lupine’s stubs are the only place it is used. You needn’t worry about this, however, until the compiler first complains about it.
Record type declarations. To marshal certain records, Lupine must NEW each of the embedded components. If one of these components is an explicit record specification (e.g., RECORD [red, green, ...]), and not a record type identifier (e.g., ColorRecord), the compiler will report a type clash. If this happens, RPC programmers must redeclare all such records to be record types.
Packed arrays of AC types. Lupine will properly translate these ill-used types (e.g., PACKED ARRAY OF STRING), but the compiler will not accept the result because of an @ applied to the packed elements.
8 References
[Cedar]Cedar Language Committee.
Cedar Mesa—Version 6T5.
File [Indigo]<CedarLang>Doc>Cedar6T5.press, 16 January 1981.
Cedar documentation is evolving, so make current inquiries; this is a founding document.
[CourierFS]Remote procedure calls.
Pilot Programmer’s Manual
[Pilot], chapter 6.3.
The Courier Functional Specification is now an integral part of the PPM.
[CourierPS]Courier: The Remote Procedure Call Protocol.
Xerox System Integration Standard XSIS 038112, December 1981.
[DES]National Bureau of Standards.
Data Encryption Standard.
FIPS Publication 46, January, 1977.
[Diplomat]Bruce Nelson.
Diplomat, Attache to Envoy.
File [Idun]<Nelson>Diplomat>Diplomat.press, version 5.0, 8 August 1980.
Envoy is an ancestor of Courier, so this document is somewhat dated.
[Grapevine]Andrew Birrell, Roy Levin, Roger Needham, and Michael Schroeder.
Grapevine, an Exercise in Distributed Computing.
Communications of the ACM 25(4): 260–74, April 1982.
[Mesa]James G. Mitchell, William Maybury, Richard Sweet.
Mesa Language Manual.
Xerox PARC technical report CSL-79-3, version 5.0, April 1979.
[Needham-Schroeder]
Roger Needham and Michael Schroeder.
Using encryption for authentication in large networks of computers.
Communications of the ACM 21(12):993–99, December, 1978.
[Pilot]Pilot Programmer’s Manual.
Xerox Office Products Division, version 8.0, March 1982.
[RPC]Bruce Nelson.
Remote Procedure Call.
Xerox PARC technical report CSL-81-9, May 1981.