SEARCH

SEARCH BY CITATION

Keywords:

  • asynchronous method call;
  • battery runtime;
  • D-Bus;
  • event-driven;
  • inter-process communication;
  • remote procedure call

SUMMARY

  1. Top of page
  2. SUMMARY
  3. 1 INTRODUCTION
  4. 2 EVENT-DRIVEN DESKTOP SOFTWARE
  5. 3 REMOTE PROCEDURE CALLS
  6. 4 EVENT-DRIVEN D-BUS METHOD CALLS
  7. 5 COMPARISON WITH EXISTING SOLUTIONS
  8. 6 CONCLUSIONS AND FUTURE DIRECTIONS
  9. ACKNOWLEDGEMENTS
  10. REFERENCES

The use of inter-process communication can yield many benefits for event-driven desktop software. However, inter-process communication (IPC) research has traditionally been focused on calculation-oriented distributed computing. This article shows that existing IPC solutions are a poor fit for the constraints imposed by event-driven programming. Our novel solution is built on top of the D-Bus system, which enjoys increased practical usage, but is still scantily researched. We focus on efficient handling of asynchronous D-Bus method calls, in a fashion similar to how Hypertext Transfer Protocol requests are treated in Asynchronous JavaScript and XML Web applications. This is supplemented with two design patterns that simplify processing of results for many kinds of asynchronous operations in event-driven software, besides just D-Bus calls. Our approach is shown to be more appropriate for event-driven applications than traditional remote procedure call systems in aspects as diverse as interactivity, threading complexity and electrical power usage. Copyright © 2013 John Wiley & Sons, Ltd.

1 INTRODUCTION

  1. Top of page
  2. SUMMARY
  3. 1 INTRODUCTION
  4. 2 EVENT-DRIVEN DESKTOP SOFTWARE
  5. 3 REMOTE PROCEDURE CALLS
  6. 4 EVENT-DRIVEN D-BUS METHOD CALLS
  7. 5 COMPARISON WITH EXISTING SOLUTIONS
  8. 6 CONCLUSIONS AND FUTURE DIRECTIONS
  9. ACKNOWLEDGEMENTS
  10. REFERENCES

Inter-process communication (IPC) technologies enable applications to invoke functionality from other processes. There are various kinds of IPC systems in existence, each best suited to a particular type of software. In this article, we present an approach to IPC that is designed for event-driven desktop applications.

In the procedural programming paradigm, functional abstractions provided by procedures are used as central building blocks of algorithms. Remote procedure calls (RPC) invoke procedures from other processes [1]. From the point of view of writing an algorithm, classic RPC appears almost exactly like calls to procedures in the local process. By using RPC, low-level network protocol details do not need to be considered in the design of clients and services, but merely which procedures to make callable from other processes.

The RPC systems exist in synchronous and asynchronous forms. In the asynchronous variants, a caller can execute concurrently with the service. More advanced asynchronous systems enable the caller to later retrieve the result of the call by polling to check when it becomes available, or blocking the caller's execution after there is no more local work to do [2]. These classic approaches are well suited to distributed computation, where their purpose is to increase the throughput of the system via parallel execution. However, the use of RPC (and IPC in general) can also be advantageous for non-calculation-oriented software.

Some resources cannot be concurrently accessed by multiple applications. A back-end process that is accessible using IPC can be used to expose such a resource as a service. As a practical example, many online real-time communication services, such as Windows Live Messenger, only allow one connection to be made to a user account at a time. Unless this connection is shared, for example, by using a back-end process as an intermediary, only a single application can communicate through the online service simultaneously. Such online services however offer functionality related to multiple different use cases, such as video calling and file transfer, in addition to textual conversations.

Refactoring traditional monolithic applications to a multitude of front-end and back-end processes, connected using IPC, can enable task-oriented division of desktop functionality. Continuing the instant messaging (IM) example, a task bar applet could be constantly running to show and allow changing the user's online status in the network. Another purpose-built application could be used to view the list of contacts reachable through the account, and a yet separate one would launch to handle a call placed on one of them. All three applications could share a single network connection to the messaging network, maintained by a back-end process.

D-Bus [3] is a full-featured IPC system. It has been adopted as the sole high-level IPC mechanism in the GNOME and KDE desktop environments, in which it has replaced CORBA and Desktop Communication Protocol (DCOP), respectively. It is also used extensively in mobile devices, such as the Nokia N9 smartphone (Nokia Oyj, Helsinki, Finland) and the One Laptop Per Child (One Laptop per Child Association, Miami, FL, USA) subnotebooks. This is in contrast with many IPC systems discussed in existing literature, which have either never graduated from research experiments to practical use, or have already faded into obsolescence. The D-Bus system supports remote method calls with return values and also has content-based publish/subscribe event delivery. D-Bus has many helpful features for the desktop use case such as service discovery and activation, and it has strong semantic and reliability guarantees. In this article, however, we focus purely on utilizing the system for method calls, specifically from the perspective of the caller.

Communication in D-Bus is performed in terms of messages, which consist of a header and a body. The header specifies how the message should be interpreted, and the body carries the actual data. In particular, the header includes most of the information needed for routing the message to its intended recipient(s). While it is possible to connect two applications together using D-Bus in a peer-to-peer fashion, typically all applications in a desktop session are connected to a shared virtual bus. The bus topology is formed by the bus daemon, which routes messages between clients that are connected to it via peer-to-peer links, as illustrated in Figure 1. The desktop-wide bus makes D-Bus a natural fit for connecting desktop applications and services together.

image

Figure 1. D-Bus bus topology.

Download figure to PowerPoint

Interactive desktop applications are typically event-driven. In this paradigm, actions and conditions such as presses of user interface buttons and incoming network communication cause events to be fired. The events are received in a single thread by an event loop, which invokes callback functions to handle them [4]. The event loop sleeps in wait for more events to process when all pending events have been processed by a handler callback. When the event loop is sleeping, the application does not consume any central processing unit (CPU) time or cause the system to wake up from low-power idle states. Dispatching the handling logic in a single thread avoids threaded programming pitfalls such as race conditions and deadlocks, and the locking overhead that is required to overcome them. The handler callbacks are typically computationally inexpensive, so the CPU concurrency offered by threads would be of little use [5].

In this article, we explore the use of D-Bus for invoking remote methods in the desktop context. We begin in Section 2 by reviewing the structure of event-driven programs and examining the constraints it places on programming. Section 3 contains a brief overview of classic RPC and how the same goals can be achieved with D-Bus, and an analysis of issues that may occur if these mechanisms are used in modern desktop software. In Section 4, we propose a set of more suitable constructs, which are centered around event-driven asynchronous invocation of methods using D-Bus. This includes descriptions of two design patterns that simplify the resulting asynchronous event handling. Although fully asynchronous, our solution avoids the time and space overhead of threads. Our solution also makes it possible for the event loop to sleep until the invocation finishes or another event happens, without periodic polling for availability of the result. Besides D-Bus method calls, the design patterns can also be used to represent other asynchronous operations, such as file input/output.

While patterns somewhat similar to ours have been documented, they are often geared for use in calculation-oriented software instead of desktop applications, or employ an inefficient design. This will be shown by the comparison in Section 5. Finally, among all presently documented solutions, our work is unparalleled due to being built using the D-Bus message bus, which is increasingly deployed in production systems today.

2 EVENT-DRIVEN DESKTOP SOFTWARE

  1. Top of page
  2. SUMMARY
  3. 1 INTRODUCTION
  4. 2 EVENT-DRIVEN DESKTOP SOFTWARE
  5. 3 REMOTE PROCEDURE CALLS
  6. 4 EVENT-DRIVEN D-BUS METHOD CALLS
  7. 5 COMPARISON WITH EXISTING SOLUTIONS
  8. 6 CONCLUSIONS AND FUTURE DIRECTIONS
  9. ACKNOWLEDGEMENTS
  10. REFERENCES

Execution of event-driven software is structured around a central event loop, which waits for events and dispatches them to handler callbacks. Events can come from external sources such as network connections and the windowing system, or be emitted from within the application. In most systems, handler callbacks are dispatched one after another in a single thread, and will not execute concurrently. This is the case, for example, in the Java Swing and SWT toolkits [6], and the GLib library that drives GNOME applications [7].

2.1 Constraints for the design of events and handler callbacks

Because the event loop and the callbacks run in the same thread, execution of callbacks delays gathering of further events (the event loop is blocked by the callbacks for their duration). This simplifies the program, because no synchronization is needed between concurrently executing threads in event handling [4]. However, it also imposes a number of constraints on how the events and their callbacks must be designed. We will now quickly explore these constraints, which we will later employ as guidelines for the use of IPC in desktop software.

2.1.1 Duration of callback execution

For applications with a graphical user interface (GUI), the time to respond to user actions is important. The user action fires an event, which is dispatched to the handler callback after callbacks have completed for all previous events. The response to the action is only visible to the user once the handling is complete and the GUI has been redrawn. Unless all this happens in at most 150–200 ms, users will notice a frustrating delay in the responsiveness of the application [8].

In modern applications, the user interface is often animated when visible, even at times of no user activity. Animation can be used to indicate progress or other changes in the status of the application, or exist purely for pleasing the eye. The event loop must be able to dispatch repainting of the interface 30–60 times per second to maintain smooth animation. Users do not directly observe the total number of frames drawn or the average rate of repaints, but they can notice if there is a significant delay between successive frames. Thus, for the animation to be pleasant, nothing must block the event loop for longer than a few dozen milliseconds, even occasionally [9].

Thus, handler callbacks should finish and allow event processing to continue in at most 150–200 ms in all interactive desktop applications, and in less than 20–30 ms in those which try to present smooth animation. Note that these figures already include the time taken to repaint the GUI, so the behind-the-scenes logic must execute even quicker.

2.1.2 Event processing order

It might be tempting to defer the processing of other outstanding events when an event for a user action is received, to attain faster response to the action. This idea is an example of event reordering.

Reordering miscellaneous ‘housekeeping’ work to happen after an user action is handled is fine. However, it is difficult for an event system to distinguish user actions from other activity, as a user action can appear in many forms, for example, as an incoming local-loop network connection. If the processing of, for example, a button press on the application's own GUI takes precedence over such a less obvious user action, the two actions will be executed in an order different from what the user requested. This might be quite significant for some actions. For example, suppose the user first wants to empty a list, and then add an item to it. If these events are processed in the reverse order, the newly added item will be lost. In general, processing of events must retain their order, that is, the event system must provide sequential consistency. Otherwise, the semantic meaning of events might be compromised.

Some event loop implementations, such as the one in GLib [7] allow assigning priorities to tasks. Non-time-critical work can be deferred by using a low priority. However, events with a given priority are processed strictly in the order in which they are received. Similarly, all implementations of the Java AWT/Swing event queue are required to preserve the order of events [10]. However, it is possible to inject application-generated events to the event loop in an inconsistent order. An example of such behavior is given in Section 3.3. Such usage should be avoided, as it destroys sequential consistency, even if that would otherwise be provided by the underlying event system.

2.1.3 Periodic tasks

Repainting a GUI periodically to create smooth animation is a good example of a periodic task that an event-driven program might want to do. Such tasks are executed by handler callbacks for events from timers, which are a way to tell the event loop to wake up at a certain interval, even in the absence of external events. The wake-up causes the system to schedule the process with the timer for execution on some CPU. The selected CPU might have had other work to do, which is then delayed until the timer event is handled. This is acceptable as long as the handler callback really does useful work. Continuing the animated GUI application example, repainting the GUI is only helpful when the window of the application is not occluded by other windows, and the display has not entered a power-saving shutoff mode. If the user does not see the animation, execution of other processes has been delayed needlessly.

Processing superfluous timer events can be harmful even in the absence of other CPU-starved work. Because eliminating the global scheduling tick in 2006, the process scheduler of the Linux kernel has been able to keep the CPU in low-power deep-sleep states for as long as all user space processes are sleeping [11]. A CPU in a deep-sleep state might consume an order of magnitude less electrical power than when awake but with no instructions to execute. The lower-power states cannot be entered or exited instantly; thus, they can only be used when there is no need to wake up for a longer amount of time, ideally at least 20–50 ms [12]. Thus, avoiding unnecessary periodic activity not only ensures the CPU is free to perform more meaningful work but also can drastically reduce the power consumption of the system.

2.2 AJAX

Asynchronous JavaScript and XML (AJAX) are a group of technologies that enable JavaScript code in a Web page to make HTTP requests to Web servers without completely reloading the page [13]. In an AJAX-based Web application, user actions such as clicking buttons cause JavaScript handler callback code to run. The callback performs the requested actions and updates the user interface. The event loop is contained in the Web browser application.

If processing a user action requires server-side processing or loading additional data, the handler code can start an asynchronous HTTP request and set a listener function to run once the request is complete. The listener function performs the actions, which require the results of the request, such as updating the view with more data. The asynchronous requests are represented in JavaScript code as XMLHttpRequest objects. Between the time an XMLHttpRequest is started and its listener function is called, the browser event loop can run, and so the user interface stays responsive (Figure 2). As an important observation, the completion of an XMLHttpRequest is essentially an event, and the listener function is the handler callback for it.

image

Figure 2. An example of Asynchronous JavaScript and XML (AJAX) communication with a Web server. XHR, XMLHttpRequest.

Download figure to PowerPoint

3 REMOTE PROCEDURE CALLS

  1. Top of page
  2. SUMMARY
  3. 1 INTRODUCTION
  4. 2 EVENT-DRIVEN DESKTOP SOFTWARE
  5. 3 REMOTE PROCEDURE CALLS
  6. 4 EVENT-DRIVEN D-BUS METHOD CALLS
  7. 5 COMPARISON WITH EXISTING SOLUTIONS
  8. 6 CONCLUSIONS AND FUTURE DIRECTIONS
  9. ACKNOWLEDGEMENTS
  10. REFERENCES

For a remote procedure to be callable similar to a local one, there must be an ordinary local function with the same type signature as the remote one. Instead of performing the computation or other action by itself, the local function forwards the call to the remote process using lower-level RPC facilities. These forwarding procedures form the client stub. The client stub type-checks and encodes the call arguments into a form that is suitable for transfer to the service process. The archetypal method for transferring the encoded call arguments to the remote process is sending a call packet over a transport layer connection, such as in the original implementation of Birrell and Nelson [1]. In the remote process, a service stub component decodes the arguments. The details of encoding and data transfer can be handled by a runtime library service that is common to all kinds of services (Figure 3).

image

Figure 3. Client and service stubs in a remote procedure calls (RPC) system.

Download figure to PowerPoint

3.1 Asynchronous remote procedure calls and return values

So far we have discussed just one side of RPC, namely that of initiating calls. The return values of procedures and the time at which their execution finishes are also significant in procedural programming. However, RPC mechanisms differ in their method return semantics. In classic RPC, the caller process is blocked until the service has informed it of the outcome of the call with a reply packet. The reply packet is translated to the return value of the local stub procedure, or if the call failed, to an exception. Only after this, client execution continues. Thus, the execution of the client and the service is synchronized, so that only one of them is executing at a time. Traditional Java Remote Method Invocation (RMI) calls are also synchronous as per this definition [14].

The synchronous semantics that are inherent to traditional remote procedure calls do not allow client execution to continue in parallel with the service. Thus, even for raw computations which do not have to wait for any other resource to be available besides CPU execution time, concurrency which could net a speedup in a multiprocessing system has been lost. Similarly, to execute some operations, external resources such as network services, peripheral hardware devices or even the human user may need to be queried. Communicating with these resources can impose long delays on the completion of an operation, even if there is idle CPU time in the system. If a process blocks to invoke such an operation, the use of CPU time may become inefficient even on a uniprocessor system. Instead of sitting idle, the calling process might be able to utilize the idle CPU time for tasks such as animating a user interface [9]. Additionally, in an event-driven application, blocking in a handler callback precludes further event handling.

To regain concurrent execution capability with synchronous RPC, multiple threads can be used in the caller process, as portrayed in Figure 4. However, the use of threads is programmatically unwieldy [2]. This approach is also not scalable to a large amount of outstanding calls, as threads with separately allocated execution stacks need to be spawned for the sole purpose of sitting waiting for a call to finish [15]. The Asynchronous Remote Method Invocation (ARMI) system achieves asynchronicity for Java RMI calls by running them in threads, in this exact fashion [14].

image

Figure 4. Using threads to continue local execution during remote procedure calls (RPC).

Download figure to PowerPoint

An alternative to running synchronous RPC in background threads is to make the remote calls itself asynchronous. This means that the calling thread is not blocked for the execution of the remote procedure, so it can continue local processing.

Some asynchronous RPC systems try to batch similar calls together to increase efficiency. This can result in call reordering [2]—a call that is made first by a client is executed later than a succeeding one. As explained in Section 2.1.2, this can be problematic if the calls result from user actions.

Many asynchronous RPC systems do not support methods, which return values to their caller. Additionally, the client is not notified of even successful execution of a procedure in some of them (‘fire and forget’ or maybe semantics) [2]. This is different from local calls and synchronous RPC, which universally support determining the outcome of procedure invocations.

The incompatibility between asynchronous remote calls and method return values is fairly obvious. In synchronous RPC, the client stub method only returns to its caller when the remote method has finished. The return value is also reported at that point. However, as the motivation for making calls asynchronously is to enable the caller to continue execution during a call, an asynchronous client stub procedure must return immediately after sending the call packet. Hence, it cannot wait for the reply packet with the return value from the service process.

To communicate the result of an asynchronous call to the caller, other means than the stub function's return value must be used. The stub immediately returning a promise object [16] is one such mechanism. A promise object is a token, which the caller later uses when it requires the result. The token can be used to wait for the call to finish, and once finished, extract the return value. This is effectively the synchronous RPC stub method split in two parts: the first merely starts the call, and the second waits for it to complete and gives the result (Figure 5). Note that the client can only wait for one promise at a time. The future mechanism [15] is similar to promises, but additionally allows waiting for multiple pending results collectively, so that they can be processed as soon as they are available. Both of these systems are characterized by the fact that in them, the results must be proactively extracted by the caller, an example of pull communication. However, an event-driven application can neither block the event-processing thread to wait for a result nor should periodically poll the token for completion (Section 2.1).

image

Figure 5. Remote procedure calls (RPC) with promise objects.

Download figure to PowerPoint

3.2 Method calls in the D-Bus system

In D-Bus [3], a message of type METHOD_CALL requests starting the invocation of method. Thus, these messages are in essence the call packets of D-Bus. The method to invoke is identified by an interface name and the name of the method in that interface. A path is also given to identify the object instance on which the method should be invoked.

The body of a METHOD_CALL message consists of the parameters passed in to the method, if it requires any. All parameters are passed by value—reference or copy–restore arguments are not supported. For this reason, the fact that D-Bus methods can produce return values is especially important.

The header of all D-Bus messages has a serial number field, which uniquely identifies messages from a single sender process (although not globally amongst all senders). The serial number of method call messages is used to associate them later with METHOD_RETURN messages, which represent successful completion of method execution. The same goes for ERROR messages, which indicate that an error/exception occurred, and the call could not complete. These messages together constitute the reply packets of D-Bus. As the ‘packet’ terminology is not used in the context of D-Bus, we will hereafter refer to D-Bus call and reply packets as messages.

The header of D-Bus reply messages contains a reply_serial field. This is set to the serial number of the call message that they should be interpreted as a response to. Note that this allows services to respond to method calls in an arbitrary order, whereas clients can still interpret the results correctly. Figure 6 illustrates the use of the reply_serial field in practice.

image

Figure 6. The function of the reply_serial field in D-Bus.

Download figure to PowerPoint

D-Bus method return messages have two purposes. First, they indicate successful completion of a method's execution. If the method produces return values, the message also serves as a carrier for them. This is similar for error messages, except that their contents is usually limited to an error code. Return messages are only generated and sent by the process that implements the method. However, errors can be generated anywhere in the messaging system, for example, because the service process has crashed or otherwise became unreachable or unresponsive. Having separate messages for starting method calls and conveying their result enables D-Bus methods to be invoked asynchronously, as clients can do local processing between sending the METHOD_CALL message and processing its result. However, nothing in the protocol requires an asynchronous implementation, as demonstrated in the next section.

A D-Bus method call can also be carried out in a fire-and-forget fashion. This is performed by setting the NO_REPLY_EXPECTED flag in the METHOD_CALL message. The service can then omit the reply as an optimization.

The D-Bus system guarantees that it delivers messages from a single sender in the same order in which they were sent. Thus, a service will receive the METHOD_CALL messages from a client in order, and the client can process the results in the order the service generates them. Thus, D-Bus does not cause event reordering problems (Section 2.1.2) at this level. However, some client implementation patterns can cause reordering during message processing in the client, as we will see in the next section.

3.3 Synchronous remote procedure calls style D-Bus method calls

So far, we have discussed the ‘wire protocol’ operation of the D-Bus messaging system for invoking remote methods. However, it is cumbersome for a client application to manually construct, send, receive, parse and reply-correlate low-level D-Bus messages related to method calling. Instead, D-Bus method calls can also be performed using library routines, which perform these steps internally, and offer a programming interface that is similar in concept to the synchronous RPC client stubs presented earlier. We will now describe two different styles for implementing these library routines and will discuss a number of potential problems that may occur when they are used in interactive desktop applications and other kinds of software.

3.3.1 Pseudo blocking

As explained in Section 2.1.1, the event loop of an interactive desktop applications must never be blocked for longer than 20–200 ms at a time. This can only be achieved if handler callbacks guarantee to always finish in at most this amount of time. Sending a D-Bus method call message can be carried out in a few milliseconds at most, as it is just a matter of sending a small amount of data over the connection to the bus daemon. Similarly, reading a method reply and interpreting it can always be performed fairly, quickly. However, the client does not have any guarantees on how long it will take for the service to process the method call and send the reply. Both the method call begin and reply messages can also end up queued inside the bus daemon for an indefinite amount of time before they reach their destination. Thus, either initiating a D-Bus method call or processing the reply for a call can be considered acceptable within a single iteration of the event loop of an interactive application. However, initiating a method call, waiting for its result and processing it, all between two refreshes of the user interface, can lead to a sluggish user experience.

Despite these suboptimal performance characteristics, real-world D-Bus client libraries such as dbus-glib, dbus-python and QtDBus provide mechanisms to invoke a D-Bus method and block until its result have been received. We will call this mechanism pseudo blocking, as in the work of McVittie [17]. This mechanism is outlined in Algorithm 1.

image

In addition to slowing down the event loop of the client, pseudo-blocking calls have the potential to completely halt the loop for a longer period of time. This occurs if the service process has locked up. In this case, the pseudo-blocking algorithm will loop until a time-out is reached [17]. In this way, the effects of programming errors that cause deadlocks can propagate from services onwards to clients.

The pseudo-blocking algorithm has to push aside D-Bus messages other than the one it is waiting for. Typically, these messages would be processed later, when the application resumes running its event loop. However, this means that these messages will only be processed after the method return message [17]. This is an instance of event reordering, which is potentially harmful for the semantic integrity of the messages and other events (Section 2.1.2).

Pseudo-blocking calls also have the potential to lock up two otherwise well-behaving processes, if they happen to call each other at the same time. Consider two processes A and B. If A uses the pseudo-blocking algorithm for making a call, it will not process incoming messages, such as method calls from B, until it has received the method reply it is expecting. But if B is doing the same, it will not process the method call A sent, and hence, will not reply to it (Figure 7). The two processes are in a cycle waiting for a reply from each other. However, neither of them will ever generate a reply, because during the wait; they just queue incoming method calls and never process them. This puts the two processes in a deadlock. We call this potential cause of deadlocks a wait-reorder cycle.

image

Figure 7. The possibility for a deadlock in a simultaneous method call between two processes.

Download figure to PowerPoint

It is safe to make a pseudo-blocking method call from client A to service B, if it is known that B will never make a similar callback to A at the same time. In particular, if B is a pure D-Bus service, and never makes outgoing method calls, it cannot participate in a wait-reorder cycle. However, the implementation of B may change over time. If B has a plug-in system, a plug-in might end up making pseudo-blocking method calls, although the core B application itself would not. If the plug-ins run in the parent application's event loop, they can stall the parent [17]. We have seen deadlocks caused by plug-ins that make pseudo-blocking calls in the development of both One Laptop per Child and Nokia mobile device operating systems.

The DCOP, the IPC system used by the KDE desktop before D-Bus, also offered pseudo-blocking method calls. DCOP had logic to detect and defuse some kinds of deadlocks caused by circular calls based on a call stack ID. This functionality protected against the kind of deadlock where A makes a method call to B, and B calls back to A as a part of processing the call. Including this mechanism in D-Bus was discussed, but it was ultimately deemed too naive. In particular, it did not avoid the deadlock in the case where A and B call each other at roughly the same time, but the calls are otherwise unrelated [18].

3.3.2 Reentrant pseudo-blocking

Some D-Bus client libraries provide a reentrant variant of the pseudo-blocking mechanism. An example of this is the QDBus::BlockWithGui call mode of the QDBus library [19]. This was also the only available behavior in ORBit, the CORBA-compliant IPC system used by the GNOME desktop before D-Bus [20]. In this variant, instead of pushing messages other than the desired reply to a queue, they are processed such as the event loop was running again, along with other events [17]. Hence the name: the event loop is effectively re-entered for the duration of the call. Switching to this mode might seem similar to a relatively straightforward way to fix the message reordering and deadlockability issues of classic pseudo-blocking.

In the design by contract ideology of Meyer [21], the class invariant is a condition that must always hold in an object of a class unless a method has currently been invoked on it. Typically, class methods have restored the invariant by the time they return, but it might not hold at all times during their execution. An example of such a situation can be found in Figure 8.

image

Figure 8. An example of a class invariant being temporarily broken: The invariant of the account class is broken when the withdrawal is recorded, which is performed before the call to charge a service fee. The invariant is reestablished only after the call, by adjusting the balance. Thus, the invariant is not satisfied during a possible reentrant method invocation.

Download figure to PowerPoint

Now, if an object makes reentrant blocking calls in one of its methods, it might have to process calls to its own methods as a part of the reentrant event processing. These method calls expect that the class invariant holds. Thus, even though the original method has not returned to the event loop, it has to have restored the class invariant. This requirement is fairly subtle, and can be hard or even outright impossible to fulfill. Thus, we consider reentrant pseudo-blocking behavior too unpredictable, and hence something that should be avoided.

4 EVENT-DRIVEN D-BUS METHOD CALLS

  1. Top of page
  2. SUMMARY
  3. 1 INTRODUCTION
  4. 2 EVENT-DRIVEN DESKTOP SOFTWARE
  5. 3 REMOTE PROCEDURE CALLS
  6. 4 EVENT-DRIVEN D-BUS METHOD CALLS
  7. 5 COMPARISON WITH EXISTING SOLUTIONS
  8. 6 CONCLUSIONS AND FUTURE DIRECTIONS
  9. ACKNOWLEDGEMENTS
  10. REFERENCES

The Telepathy framework [22] is an implementation of online real-time communication functionality, such as IM, built around the D-Bus IPC system. The framework consists of protocol back-end services, which handle protocol-specific network communication, and various other services, which have purposes such as conversation logging and account management. Any number of user interface clients can share a single set of connections to the messaging networks, by accessing the protocol back-ends over D-Bus.

To accommodate convenient access to the Telepathy back-end services, we have implemented a set of client libraries. The Telepathy-Qt library [23] is built around the lower-level QDBus library [19], and is used in front-end software of the KDE desktop and the Nokia N9 phone. The Telepathy-Qt library has served as the proving ground for the ideas that are presented later.

Due to the problems described earlier, the Telepathy-Qt library neither internally uses pseudo-blocking D-Bus method calls nor forces the host application to do so. Instead, as explained in the following section, we handle D-Bus method calls in a similar fashion as HTTP requests are made in AJAX Web applications. Not using pseudo blocking complicates application code especially in cases where the context that led to a call is needed to interpret the results correctly. In Section 4.2, we present an object-oriented design pattern that helps associate results with their context. This pattern is applicable to many kinds of asynchronous operations, not just D-Bus method calls. The composite extension of this pattern (Section 4.3) further ameliorates the complexity by enabling applications to process results from multiple operations collectively. Section 4.4 demonstrates that the patterns are widely used in practice, for both D-Bus and Telepathy, and otherwise.

4.1 Asynchronous D-Bus method invocation model

Most of the functionality offered by the Telepathy-Qt library is implemented by accessing D-Bus services. Method calls to these services cannot be made using the pseudo-blocking algorithm (Section 3.3), as that would make the library prone to cause sluggish event response, deadlocks and event reordering, or hard-to-predict invariant validity issues (Section 3.3.2). Instead, we invoke D-Bus methods using the QDBusConnection::asyncCall() functionality from the underlying Qt framework [19].

The asyncCall() method encodes the call arguments in a METHOD_CALL message, sends it and returns to the caller immediately, without waiting for the result. These steps are analogous to an AJAX application opening and sending an XMLHttpRequest. As there is no blocking until the result arrives, the asyncCall() method finishes in a known-short time and hence, can be used without danger of stalling the event loop.

The return value of asyncCall() is a QDBusPendingCall object. It can be used in two ways. First, one can use it to create a QDBusPendingReply object. Instances of this class are such Liskov's promise objects (Section 3.1): they have functionality for blocking until the result is ready and then, extracting it. However, an event-driven application can never afford to block indefinitely. Hence, this promise-style functionality is no more useful for our purposes than pseudo blocking.

In the Telepathy-Qt library, the QDBusPendingCall objects are used to initialize instances of the QDBusPendingCallWatcher class. These objects emit the finished() event once the result of the corresponding call is available. This event can be connected to a handler, which creates a QDBusPendingReply object from the watcher and can extract the result from it. Effectively, the results are pushed to the handler. Obviously, the finished() event needs to be emitted, and the reply object set to contain the result of a call based on a reply D-Bus message (Section 3.2). This is carried out internally by the Qt framework, which receives and processes the reply messages alongside, for example, keyboard and mouse events from the windowing system.

Once invoked, the handler for the finished() event of a watcher object can extract and process the reply without blocking, similarly to an AJAX listener function. Until the event loop invokes the handler, it can process other events, or in the absence of any, sleep. This is ideal for the target environments of the Telepathy-Qt library: GUI applications stay interactive, and mobile devices can utilize low-power states even during D-Bus method calls.

While this asynchronous call model avoids the functional problems of pseudo-blocking calls, additional programming complexity is introduced by the need to associate the finish events of watcher objects with the original calls. With pseudo-blocking calls, the context that led to a call is readily available when interpreting the result (Algorithm 2). However, an event handler that ends up calling asyncCall() needs to finish promptly to keep the event loop running. When the handler returns to the event loop, the context that led to the call is lost, unless it is explicitly saved, as in Algorithm 3. The handler for the finish event needs to restore the saved context before processing the result (Algorithm 4). Next, we will present two design patterns that mitigate this complexity in application code.

image
image
image

4.2 Pending operation objects

It is usually necessary to know to which invocation of a method results belong, to correctly interpret them. The REPLY_SERIAL field of low-level D-Bus messages is used to correlate the messages that start method calls with their replies (Section 3.2). In our use of the Qt asyncCall() facility, we create one QDBusPendingCallWatcher object for each method call. Hence, we can use the memory address of the watcher objects as a unique identifier (UID) for matching method calls with their results. However, the context in which the call was made must to be saved separately and then retrieved for each watcher object when interpreting the results. This can become tedious.

In the public programming interface of the Telepathy-Qt library, running asynchronous operations are represented as objects from the PendingOperation class hierarchy (hereafter called PendingOps). The functionality of the PendingOperation base class overlaps that of both QDBusPendingReply and QDBusPendingCallWatcher objects: there is an event that is emitted when the operation finishes, and methods for querying if the operation finished successfully, or with some error [23]. However, extracting the result in the successful case is performed using the functionality of result-type specific subclasses. For example, the PendingAccount and PendingConnection classes are used for operations that create IM accounts and connections to them, respectively (Figure 9). Using handwritten subclasses for result extraction gives more freedom in representing the result than QDBusPendingReply, which is tied to the low-level D-Bus type of the result. For example, Telepathy-Qt reports time stamps as QDateTime objects, although they are encoded on the D-Bus level as plain integers carrying UNIX time.

image

Figure 9. Structure of the pending operation objects pattern.

Download figure to PowerPoint

The subclasses of PendingOperation are also used to mitigate the primary source of complexity of asynchronous invocations: remembering why an operation was started, to decide what to do when it finishes. For example, IM contact information can be requested in Telepathy in various ways, including:

  • by the UID of the contact in the network (juliet@capulet.lit/chamber),

  • by a URI for the contact (xmpp:juliet@capulet.lit),

  • matching an address book field (adr:Verona,Italy), and

  • requesting supplementary information for an already known contact.

In all cases, the contact information is delivered via a PendingContacts instance. Instances of this class will automatically save the details of the original query, and allow accessing them together with the result. Consider the first option: request by UID. Common practice in IM networks is to treat UIDs case-insensitively, so that they are normalized to all lowercase. Additionally, for example, with the Extensible Messaging and Presence Protocol, clients can be contacted by a bare address, but responses will include a resource component that identifies different instances connected to a particular account. Thus, a request for Juliet@Capulet.LIT might result in a contact with the UID juliet@capulet.lit/chamber. The corresponding PendingContacts instance will report the UID that the request was made for, in addition to the resulting contact object, which has the normalized identifier. Based on this information, the caller knows which contact object to associated with each requested ID.

When used for D-Bus calls, PendingOps fire their finish event as a result of an internal QDBusPendingCallWatcher finishing. However, PendingOps are not only used for D-Bus calls but also for operations such as file system access. For some PendingOps, this can even vary at runtime. Consider an operation that retrieves avatar images for IM contacts. If these images have already been retrieved, the operation can finish instantly with in-memory data. If not, the image data might be read from a shared cache file. Only if the images are not found in any cache, a D-Bus call will be made to the protocol back-end, resulting in download from the IM server, and the use of QDBus watchers and pending replies. In all cases, the results are given consistently through a PendingOps.

The original naive implementation of PendingOps did not adequately consider operations that may finish instantly. In this case, the PendingOps would emit the finish event before there is a chance to register a handler for it. This could be worked around by checking new PendingOps for being finished already, and if so, invoking the handler manually (Algorithm 5). This could easily be forgotten, leading to waiting for the handler to be invoked forever. The need to invoke the handler from the method that starts the operation also leads to invariant validity issues similar to those encountered with reentrant pseudo-blocking (Section 3.3.2). To solve these issues, the current implementation of PendingOps in Telepathy-Qt ensures that

  • finished() is emitted at the earliest on the first return to the event loop after an operation is started;

  • handlers are only invoked through the event loop; and

  • PendingOps subclass bugs cannot cause handlers to be invoked multiple times for a single operation.

image

We will now generalize the use of PendingOps in the Telepathy-Qt library as a formal design pattern, similarly to the descriptions in the ‘Gang of Four’ book [24].

4.2.1 Pattern: pending operation objects
4.2.1.1 Intent

Represent asynchronous operations in a uniform fashion. Mitigate the complexity of restoring the context of an asynchronous call when interpreting the results. Avoid race condition possibilities.

4.2.1.2 Problem

The context that led to starting an asynchronous operation is lost when returning to the event loop to wait for the results. There needs to be a way to associate the results from the finish event with the request context, to correctly interpret them. Also, an operations may finish before there is a chance to hook up a handler to process its result.

4.2.1.3 Participants
  • The base class for all pending operation objects. This includes the common attributes and the event that is used to signal the completion of the operation. This is the PendingOperation class in the Telepathy-Qt library, as shown in Figure 9.

  • Derived classes for each type of operation result. These make it possible to retrieve the value of the result in an appropriate form (cf. classes PendingAccount and PendingConnection in Figure 9).

  • Classes that need to have asynchronous operations. These create and return the pending operation object instances when an asynchronous operation is started on them (cf. classes AccountManager and Account in Figure 9).

4.2.1.4 Collaborations

Results for asynchronous operation invocations are delivered by having an event announced on the corresponding PendingOps. Client code extracts the values of results using methods declared in the result type specific subclasses. The subclasses save commonly needed parameters of invocations and make them available together with the results. The base class ensures that all operations finish in a uniform fashion from handlers’ point of view. The structure of the pending operation objects pattern is illustrated in Figure 9, with the class names corresponding to those in the Telepathy-Qt library.

4.2.1.5 Consequences

Completion of all asynchronous operations is signaled in a uniform way. This avoids having to re-learn how completion is indicated for each operation and therefore, can make a library interface with many asynchronous operations easier to understand. However, creation of the pending operation objects incurs a space and time cost. This cost may be greater than, for example, that of just using the memory address of the watcher object as a key to look up separately saved context of the call. If performing the operation itself is very cheap (e.g., instant finish with a cached result), this might end up being a significant portion of the resources spent.

4.3 Composite pending operation

Sometimes, a client requires multiple pending operations to finish before it can process results from any of them. In many cases, the operations do not depend on results from each other and can be started in any order, before any one of them finishes. However, if the operations are handled independently, their handlers end up having duplicated code along the lines of ‘have the other operations finished already’ (Algorithms 6–8).

image
image
image

Telepathy-Qt includes the PendingComposite class, which makes it possible to assign a single handler for a group of asynchronous operations. The handler is invoked when all of the operations are complete, or one of them has encountered an error. Thus, no separate handlers are needed for operations, which produce results that are only useful together with results from other operations. On successful completion, a single handler can process all of the results together without further checks.

The PendingComposite class only aggregates the finish events. There is no accumulation of operation result values (other than errors, with one error causing the entire composite operation to fail). This makes it possible to use the class with PendingOps for operations with varying result types, which generally cannot be accumulated. The result values can be extracted from the individual operations as needed, as the composite operation finishing guarantees that all part operations are complete.

A formal description of the aggregation pattern that is implemented as the PendingComposite class follows.

4.3.1 Pattern: composite pending operation
4.3.1.1 Intent

Make it easy to continue processing once a number of related asynchronous operations have finished.

4.3.1.2 Problem

Resuming processing once an asynchronous operation has finished is by itself complex to achieve, because of the need to identify the request that led to a particular result. In the presence of multiple related operations, this becomes even more difficult, as the operations can finish in any order, with any one of them being the final one that should trigger result processing. Because of this, the result handler for each operation needs to check if the other operations are finished already.

4.3.1.3 Participants
  • PendingOps for arbitrary result types, which represent the real running tasks (Examples in Figure 10: PendingAccount, PendingConnection).

  • A composite PendingOps container object, which appears as another PendingOps to users (Figure 10 and Telepathy-Qt: PendingComposite).

image

Figure 10. Structure of the composite pending operation pattern.

Download figure to PowerPoint

4.3.1.4 Collaborations

The composite operation object will keep track of individual subtasks finishing. To do this, it holds references to them, as shown in Figure 10. Once all of the tasks are performed, the composite operation will announce itself to have finished. The handler can then extract any result values required from the original PendingOps.

4.3.1.5 Consequences

Waiting for a set of asynchronous operations to finish is as easy as waiting for just one. Essentially, there is just one asynchronous operation instead of multiple ones contributing to software complexity. However, this might make it tempting to overuse composite operations, such that the results of some operations that could be utilized immediately are left waiting for unrelated operations to complete.

4.4 Applicability of the patterns

As of version 0.9.3 (July 2012), the Telepathy-Qt library interface has 25 PendingOperation subclasses. These are used for 199 public asynchronous operations in 39 classes [23]. The projects that use Telepathy-Qt have created numerous private PendingOperation subclasses for application-specific tasks. One example is interfacing with the KDE wallet service for the purpose of IM account password storage [25]. Being derived from the common base class, it would also be possible to use these classes with Telepathy-Qt's PendingComposite convenience class.

The PendingComposite implementation of the composite pending operation pattern is used in the internal implementation of nine Telepathy-Qt library classes, one unit test, and several applications. As the notion of asynchronous operations is similar, a suitable implementation of the pattern could be useful for AJAX applications that make concurrent HTTP requests and could need to combine their results.

The Telepathy-Qt variant of the pending operation pattern was partially inspired by KDE's KJob family of classes. KJob is used widely in several components of the KDE desktop, for example, for representing asynchronous filesystem operations in the KIO subsystem [26]. KJob has a sophisticated set of features, including progress reporting, intermediate results, and suspending/resuming execution [27]. However, the core pattern is still the same as in our PendingOps, as shown by Table 1.

Table 1. The pending operation objects pattern in Telepathy-Qt and KDE.
 Telepathy-QtKDE
Common base classPendingOperationKJob
Event when finishedfinished()result()
Check for errorisError()error() != 0
Error codeerror()error()
Error descriptionerrorMessage()errorString()
Result extractionSubclass specificSubclass specific

5 COMPARISON WITH EXISTING SOLUTIONS

  1. Top of page
  2. SUMMARY
  3. 1 INTRODUCTION
  4. 2 EVENT-DRIVEN DESKTOP SOFTWARE
  5. 3 REMOTE PROCEDURE CALLS
  6. 4 EVENT-DRIVEN D-BUS METHOD CALLS
  7. 5 COMPARISON WITH EXISTING SOLUTIONS
  8. 6 CONCLUSIONS AND FUTURE DIRECTIONS
  9. ACKNOWLEDGEMENTS
  10. REFERENCES

Our solution is built from the ground up with a focus on efficient handling of asynchronous D-Bus method calls. Here, we compare the resulting design with a couple of documented solutions that share a few properties with ours.

5.1 KJob

As noted before, KDE's KJob objects and Telepathy-Qt's PendingOps have a similar asynchronous execution pattern. Interestingly, unlike our PendingOps, KJob supports pseudo-synchronous execution via its exec() method. This method runs the operation in a nested event loop, so it is subject to the dangers of reentrancy (Section 3.3.2). Indeed, the documentation of this method repeatedly advises against using it at all [27].

KJob has a plethora of features not present in the Telepathy-Qt implementation of PendingOps. These features are only supported by some KJob subclasses. For example, not all operations represented as KJobs have the suspend/resume capability [27]. Besides consistency between operations, we chose the minimalist design for reasons of efficiency. The KDE libraries are mostly designed for full-blown PCs, whereas Telepathy-Qt is also used in smaller mobile devices with less random-access memory and lower CPU performance. The PendingOperation base class in Telepathy-Qt 0.9.3 has just a few member variables: one reference-counted pointer, two character strings, and a boolean. The data members in the latest development version of KJob include several child objects and two skip-list containers, in addition to a bunch of primitive variables [28]. The resulting space and time cost might make KJob less appropriate to use in constrained environments than our minimal implementation. This mostly has an effect on operations that may finish really quickly, for example, by returning cached results. On the other hand, the progress reporting, suspend/resume and canceling capabilities of KJob make it better suited to operations that run for a user-visible amount of time (seconds to minutes).

5.2 Event-driven usage of blocking systems

Some implementations of promise/future objects (Section 3.1) allow specifying the maximum time to wait for the operation that blocks until the result is available. If the time-out is reached, the blocking is interrupted, and the application can try again after doing some other processing. Some systems even allow a time-out of zero, which means just checking if the result would be available, and not blocking even if it is still pending. This could be used together with a periodic timer event (Section 2.1.3) to repeatedly check for a result, without blocking the event loop at all. However, until the result arrives, the CPU time and electrical energy consumed by the periodic wake-ups are simply wasted. These useless wake-ups can be reduced by lengthening the timer interval. However, as the result is only processed at the first periodic check after it has arrived, longer intervals lead to an increased latency for result handling. In our approach, the Qt event loop processes the D-Bus reply messages and immediately causes the handlers to be invoked via the QDBusPendingCallWatcher and PendingOperation objects, in a ‘push’ fashion. Thus, there are no wasted wake-ups, but neither is there any added latency.

The ARMI [14] is an influential asynchronous wrapper for the Java RMI system. Remote method return values are delivered in ARMI through a mailbox, which is a container for yet-unclaimed method results. The results are claimed from the mailbox against a receipt, which is a UID generated by the system for each remote call. Receipts and the mailbox together provide the functionality of promise objects (Section 3.1): checking if a result is available, waiting for one to become available, and extracting it. Additionally, a callback function can be set on the mailbox for each receipt. The callback is invoked when the result for the receipt arrives, with the receipt and the result as arguments. Thus, the callback becomes a handler for the event of the result arriving. The parameters the callback is given make it equivalent to our initial idea of using the memory addresses of QDBusPendingCallWatcher objects as identifiers for associating method calls with their results (Section 4.2). However, with this idea, all of the required call context needs to be saved manually, whereas our PendingOps subclasses can be designed to automatically store useful state such as call arguments. Additionally, the choice between promise-style or event-driven result delivery may lead to reordering in processing of results: results that are delivered with events are processed as soon as they arrive (‘push’), but other results only when the client goes to the mailbox with the corresponding receipt (‘pull’). ARMI might be better suited to non-interactive applications, where the general activity performed is that of batch processing, and hence, the promise-style operations can be used extensively.

The CentiJ system includes another asynchronous RMI wrapper [29]. Their approach makes use of the Observer and Observable interfaces of the Java standard library. In CentiJ, asynchronous remote operations are represented as observable objects. When a call is made and a result arrives, the observers registered with the corresponding operation object are invoked. Thus, the CentiJ result delivery pattern is exclusively event-driven, such as ours. However, as with ARMI, CentiJ has no further provision of call context—result association besides that of a UID for each call.

Both the ARMI and CentiJ wrappers make use of the synchronous RMI system to implement the underlying communication between clients and services. The asynchronicity is achieved by running the synchronous RMI calls in threads, similarly to the idea of a client creating a thread for the purpose of running synchronous RPC (Section 3.1). However, ARMI and CentiJ create the threads internally, and the complexity of managing them does not propagate to user code. The threads still incur a high overhead, as a new thread is created for each call. Such as the cost of KJobs, the thread overhead is most noticeable with quick, lightweight operations and is less of a problem for longer-running and/or resource-heavy tasks.

In ARMI and CentiJ, the result callback/observer is invoked from the hidden thread that makes the synchronous RMI call. Thus, result handling needs to synchronize access to any data shared with application-visible threads, such as the original thread that started the asynchronous call. This is unlike PendingOps, which are designed to allow handlers in the thread of the event loop to make remote calls. As the event loop thread also receives the result D-Bus messages and invokes the handlers through PendingOps, calls are both made and their results processed in a single thread. This simplifies applications, as data of the caller thread can be accessed and GUI elements updated safely without explicit synchronization. However, in calculation-oriented applications that already employ threads to increase throughput via concurrency, data synchronization will be needed in any case. Again, our approach benefits event-driven desktop software, whereas the existing solutions can be perfectly workable for heavy batch calculations.

5.3 Asynchronous operations in the .NET Framework

The IAsyncResult interface in the Microsoft .NET Framework [30] enables result delivery for asynchronous tasks. Promise-style waiting for results is supported along with completion notification through a callback. Of these, the callback alternative is suitable for event-driven software. IAsyncResult callbacks are conceptually similar to the handlers for PendingOps finish events, but they additionally have the responsibility of cleaning up the resources held by the operations [31]. The resources are freed using a single abstract method, which also extracts the results, so operation-specific details do not have to be considered. However, the requirement for invoking this operation necessitates assigning a handler for each operation. This is unlike PendingOps, which can be used in a true fire-and-forget way. This is useful in cases where the side effects, for example, hanging up a VoIP call, are the important bit, and there is either no result value, or it is not interesting. Additionally, results can be utilized by multiple PendingOps finish handlers. In contrast, the IAsyncResult cleanup/result extraction operation must be called only once, so multiple callbacks cannot be used [31]. This is not just a theoretical drawback: Telepathy-Qt uses multiple result handlers for a single operation in numerous cases.

As with ARMI and CentiJ, typical implementations of the IAsyncResult interface run the operation in a background thread [31]. The callback is also invoked in this thread. This is problematic for GUI applications, as a Windows Forms control can only ever be accessed from the thread that created it [32]. Thus, GUI updates following asynchronous operations must be carried out with complex work-arounds. One such work-around by Taulty [33] is reusable with any operation, and transparently arranges for the entire result callback to be invoked in the GUI thread. With this work-around, the calls are effectively started and finished in the GUI thread, as with PendingOps. However, the use of background threads still carries a performance overhead.

The promise-style functionality of the .NET IAsyncResult interface supports waiting for multiple results to become available at once. This is performed using the WaitHandle.WaitAll method. Some early asynchronous RPC systems (e.g., that of Satya and Siegel [34]) had a similar capability to block for multiple results simultaneously. However, none of these systems makes it possible to collect multiple finish events without blocking, such as our event-driven composite pending operation pattern (Section 4.3).

6 CONCLUSIONS AND FUTURE DIRECTIONS

  1. Top of page
  2. SUMMARY
  3. 1 INTRODUCTION
  4. 2 EVENT-DRIVEN DESKTOP SOFTWARE
  5. 3 REMOTE PROCEDURE CALLS
  6. 4 EVENT-DRIVEN D-BUS METHOD CALLS
  7. 5 COMPARISON WITH EXISTING SOLUTIONS
  8. 6 CONCLUSIONS AND FUTURE DIRECTIONS
  9. ACKNOWLEDGEMENTS
  10. REFERENCES

Inter-process communication technologies are not only helpful for distributed computation but also for building desktop software in a modular fashion. In this article, it was shown that traditional RPC-style communication mechanisms are a poor fit for the requirements of event-driven desktop applications. This is due to the constraints imposed by maintaining interactivity, semantic integrity, and low electrical power usage: no lengthy blocking operations, no event reordering, and no periodic polling. As a better option, a novel remote method invocation solution was proposed, built on top of the D-Bus message bus system. D-Bus is increasingly used in personal computing and mobile systems today, such as the GNOME and KDE desktops, and the OLPC and Nokia Linux mobile devices. The proposed approach adapts an asynchronous invocation model similar to that of the well-established AJAX techniques, and is applicable to many kinds of operations besides D-Bus method calls. This approach, formulated as the generalized pending operation patterns, has an emphasis on event handling convenience and efficiency, but still obeys the constraints of the event-driven programming environment.

The particular benefits of our solution stem from the fact that it has been designed from the ground up for the event-driven use case. Typical desktop software is relatively computationally inexpensive and hence, has little use for the CPU concurrency that multithreading could offer. Unlike many existing solutions, our asynchronous remote call mechanism does not require the use of threads (but is compatible with them if needed). This avoids the need for data synchronization and allows freely manipulating GUI components, which can only be accessed from a single thread in common toolkits. However, more computationally intensive kinds of applications might be multithreaded anyway and hence, have no benefit from the simplicity of our single-threaded approach. Our solution also requires the presence and frequent execution of a central event loop, which non-interactive software often does not provide.

One of the key features of our pending operation objects pattern is helping save relevant calling context for interpreting the result. Although our asynchronous invocation model is similar to AJAX, the limited context transfer functionality would be of little benefit in AJAX JavaScript code, as JavaScript supports closures at the language level. Closures and other language features that allow binding state variables beyond the level of the immediate lexical scope could solve many complexity issues of asynchronous invocation and hence, could also be useful for desktop software using D-Bus. Similarly, as shown by the Scala Futures API [35], a functional programming language allows complex ways of composing asynchronous operations together, much beyond the capabilities of our composite pending operations.

We have deliberately left the treatise of remote method errors to the level of quick remarks. This is to keep the focus on the core of our idea: push-based asynchronous method call completion notification, driven by the event loop. From this perspective, it is not significant whether the result received by a handler is that of successful execution or an error. The same goes for the distinction between methods that produce a return value and those which do not. However, beyond invoking the handler callback, error handling becomes a complex subject, with many design alternatives. For example, it might be desirable to force the callback to handle errors explicitly, similarly to the idea of checked exceptions in Java. This is actually unlike the implementation of the pending operation objects pattern in the Telepathy-Qt library. The relative merits of the various error reporting options should be considered in future research regarding the D-Bus system.

Other than presenting the composite pending operation pattern, we have stayed in the scope of a single remote method call. However, we have briefly touched the subject of operations that cause side effects beyond producing a return value. D-Bus is usually used together with object systems. In these cases, the side effects usually change the state of the object a method is called on. A future article is planned to cover the subjects of remote object state caching and object proxies in the context of D-Bus.

Describing designs for object state change notification also requires considering the other side of D-Bus besides method calls: signals, which are essentially D-Bus level events. A balanced overall description of the D-Bus system at the protocol level would be useful to establish this industry-proven message bus system in the academic context. The protocol could also be compared with some predecessors/competitors such as DCOP, CORBA, and the X11 core protocol. Besides the low-level protocol, it would also be helpful to explore the role of the D-Bus bus daemon in providing the naming and life cycle management services and semantic guarantees of the system.

In this article, we focused on design concerns for IPC in user-facing interactive applications, which usually act as D-Bus clients. We drew an analogue between AJAX HTTP requests and our asynchronous D-Bus method calls in the client side. There are also numerous design alternatives for implementing the service side, which could be explored in a separate article. An interesting parallel for the service side would be the event-driven Node.js server framework.

As a concluding remark, the ancient wisdom of using the right tool for each job seems to be very applicable to the subject of inter-process communication. In showing that event-driven desktop software is an entirely different job from the traditionally researched use cases of IPC systems, we hope that system designers can benefit from our experience of employing IPC in this area.

ACKNOWLEDGEMENTS

  1. Top of page
  2. SUMMARY
  3. 1 INTRODUCTION
  4. 2 EVENT-DRIVEN DESKTOP SOFTWARE
  5. 3 REMOTE PROCEDURE CALLS
  6. 4 EVENT-DRIVEN D-BUS METHOD CALLS
  7. 5 COMPARISON WITH EXISTING SOLUTIONS
  8. 6 CONCLUSIONS AND FUTURE DIRECTIONS
  9. ACKNOWLEDGEMENTS
  10. REFERENCES

The design patterns described in Section 4 have been presented earlier in the principal author's Master of Science thesis [36]. The development of the thesis was supported financially by Collabora Ltd., a Cambridge, United Kingdom based software consultancy specializing in bringing companies and the open-source software community together.

REFERENCES

  1. Top of page
  2. SUMMARY
  3. 1 INTRODUCTION
  4. 2 EVENT-DRIVEN DESKTOP SOFTWARE
  5. 3 REMOTE PROCEDURE CALLS
  6. 4 EVENT-DRIVEN D-BUS METHOD CALLS
  7. 5 COMPARISON WITH EXISTING SOLUTIONS
  8. 6 CONCLUSIONS AND FUTURE DIRECTIONS
  9. ACKNOWLEDGEMENTS
  10. REFERENCES