6.2.1 Inter-process communication

As early as 1976, White suggested the use of remote procedures to facilitate out-of-process calls, instead of requiring developers to directly send a data stream compatible with a certain network protocol. White’s proposal called for a standardized type system used for marshalling, a choice between synchronous and asynchronous calls, and for modifying compilers “to provide minor variants of their normal procedure-calling constructs for addressing remote procedures” (thus generating code for calling remote procedures). The Open Group’s Distributed Computing Environment (DCE) introduced an interface description language from which procedural proxies were generated, for use with inter-process and inter-machine communication. Before DCE, developers often wrote tools that generated such proxies directly from C header files (Hludzinski 1998).1 When Sony Ericsson created a new IDL compiler that could generate proxies for inter-process communication automatically, there was thus ample precedent for this effort.

The proxies in ECMX work similarly to proxies in other systems. Once set up, a client-side proxy masquerades as the remote object, and passes all invocations to the server-side proxy, which invokes the call on the true remote object. The server-side proxy ensures that the return value is transmitted back to the client-side proxy, which returns this value to the client. ECMX does not support a concept similar to COM’s in-process handlers, which can elect to handle some operations locally in order to boost performance. (A client-side proxy is simply called a “proxy” in ECMX, and a server-side proxy is called a “stub,” which is consistent with the naming convention used by COM.)

Clients normally need not concern themselves with the mechanics of setting up proxies. A server always provides an object, running in the client’s context, whose sole purpose is to set up the connection and provide a client-side proxy. Such objects are called managers. A manager encapsulates all knowledge required to set up a connection with the server, including the process identifier of the server process.

As part of the build process, proxy classes are generated by the IDL compiler for all interfaces that should be available across process boundaries. (There is not enough type information available at runtime to dynamically synthesize client-side proxies and use generic server-side proxies, a technique exemplified by Java’s RMI and .NET Remoting, which are described in Chapter 5.2) There is only one client-side proxy class and one server-side proxy class per interface, but every new connection between a client and a server gets freshly created client-side and server-side proxy instances.

In OPA, every process is associated with an object implementing the IApplication interface. An OPA process can either elect to process messages sent from other processes itself, or it can let the system handle this aspect by deferring message handling to a system-provided message loop. This message loop routes messages not addressed to a specific recipient to the IApplication::OnReceivedMsg() operation of the object associated with the process. Messages processed by the system message loop can also be addressed to a specific handler, which is an object implementing the IHandler interface, or one of its descendants. Such messages are associated with OPA sessions. The system message loop routes messages by consulting a per-process table mapping session identifiers to handler objects. Server-side proxies are OPA handlers, and the system message loop thus routes messages sent from client-side proxies directly to the appropriate server-side proxies.

The code for setting up and tearing down proxy connections is part of the ECMX runtime system. When a connection is set up from a client context, a message is sent to the server asking it to create a session linked to a newly created server-side proxy. The server sends a message back informing the client of the session identifier, which then creates a client-side proxy, initialized with the session identifier and the process identifier of the server. When a client-side proxy determines that there are no live references to it, it destroys itself, and asks the server-side proxy to do likewise. (Proxies are reference-counted like all other ECMX objects.)

ECMX does not use a formalized wire format. Primitive arguments to an operation, such as integers, are serialized by the client-side proxy simply by putting them in a C structure and copying its contents byte-for-byte into a message. The server-side proxy uses the same C structure to deserialize the primitive arguments.3 Pointers to arrays and strings are marshaled by copying their contents into the message. (Their runtime size must be given as a separate argument, and this argument must be designated as such in the IDL file.4)

Interface references can also be marshalled, and are always passed by reference. A server receiving an interface reference accesses the client-side object through proxies in the reverse direction, which are automatically set up by the generated code (illustrated in Figure 2.2). If a process A sends a local interface reference to a process B, which sends this interface reference to both processes C and A, process C will receive a client-side proxy communicating directly with a server-side proxy in process A, and process A will receive a direct reference to its local interface reference.

ECMX supports both synchronous and asynchronous invocation semantics. A synchronous call is realized by blocking the client process until the server sends a response. In common with other similar systems, such calls are several orders of magnitude slower than direct function calls (an average response time of 350 microseconds, which includes marshalling and context switches, has been observed using an in-circuit debugger). Asynchronous operations allow the client to continue executing while the server processes the request. Passing interface references to a remote object proves especially useful when using asynchronous operations, as it gives the server an opportunity to respond. (There is a pattern for subscriptions, which are used by clients that wish to subscribe to events from a server, such as file system changes.)

Synchronous operations, while convenient, pose a problem in the form of deadlocks (as in most other systems). A process invoking a synchronous operation on a client-side proxy is blocked, and will only resume execution after a response message has been received. If the target process is blocked waiting for a reply from the first process, neither process will resume execution. (An arbitrary number of processes can be involved in a deadlock, if the target process is only indirectly waiting for a response from the first process.)

Deadlocks could be avoided by electing to consider arbitrary messages while awaiting a reply, and not just the expected return message. This is not done for a few reasons, chief among them the desire to avoid reentrancy issues. A client invoking a synchronous operation on an object is not necessarily in a consistent state at the time of the invocation, and considering arbitrary messages could thus lead to unexpected behavior.

The sizes of the call stacks in Sony Ericsson’s system are fixed, making it possible to write beyond their confines. Doing so typically results in unintended and likely erroneous behavior, which makes it imperative that stack growth is kept under control. Acting on arbitrary messages while waiting for a response message to a synchronous request would risk writing past the end of the call stack of the currently executing process.

Footnotes

  1. This heritage may go some way toward explaining why many interface description languages, including those used by DCE, COM and ECM/ECMX, are reminiscent of C.
  2. Space is at a premium in most embedded systems, and metadata in the form of runtime-accessible type information does occupy space. However, so does generated code, especially highly repetitive, verbose code typical of proxies for inter-process communication. Including more runtime type information makes it possible to use domain-agnostic system classes and runtime-synthesized objects in lieu of automatically generated classes. This approach may actually use less space overall, and has other benefits as well, such as enabling very late binding and frameworks that rely on naming conventions.
  3. This is only true for classes written in native code. Remote objects written in Java use server-side proxies mostly written in Java. See section 6.2.2.
  4. Null-terminated strings need no argument specifying their size, as the runtime system is capable of gathering this information from the string itself.