Algorithmic Verification of Asynchronous Programs

Reading time: 5 minute
...

📝 Original Info

  • Title: Algorithmic Verification of Asynchronous Programs
  • ArXiv ID: 1011.0551
  • Date: 2010-01-01
  • Authors: Thomas A. Henzinger, Rupak Majumdar, and Thomas A. Henzinger —

📝 Abstract

Asynchronous programming is a ubiquitous systems programming idiom to manage concurrent interactions with the environment. In this style, instead of waiting for time-consuming operations to complete, the programmer makes a non-blocking call to the operation and posts a callback task to a task buffer that is executed later when the time-consuming operation completes. A co-operative scheduler mediates the interaction by picking and executing callback tasks from the task buffer to completion (and these callbacks can post further callbacks to be executed later). Writing correct asynchronous programs is hard because the use of callbacks, while efficient, obscures program control flow. We provide a formal model underlying asynchronous programs and study verification problems for this model. We show that the safety verification problem for finite-data asynchronous programs is expspace-complete. We show that liveness verification for finite-data asynchronous programs is decidable and polynomial-time equivalent to Petri Net reachability. Decidability is not obvious, since even if the data is finite-state, asynchronous programs constitute infinite-state transition systems: both the program stack and the task buffer of pending asynchronous calls can be potentially unbounded. Our main technical construction is a polynomial-time semantics-preserving reduction from asynchronous programs to Petri Nets and conversely. The reduction allows the use of algorithmic techniques on Petri Nets to the verification of asynchronous programs. We also study several extensions to the basic models of asynchronous programs that are inspired by additional capabilities provided by implementations of asynchronous libraries, and classify the decidability and undecidability of verification questions on these extensions.

💡 Deep Analysis

Figure 1

📄 Full Content

Asynchronous programming is a ubiquitous idiom to manage concurrent interactions with the environment with low overhead. In this style of programming, rather than waiting for a time-consuming operation to complete, the programmer can make asynchronous proce-A:2 dure calls which are stored in a task buffer pending for later execution, instead of being executed right away. We call handlers those procedures that are asynchronously called by the program. In addition, the programmer can also make the usual synchronous procedure calls where the caller blocks until the callee finishes. A co-operative scheduler repeatedly picks pending handler instances from the task buffer and executes them atomically to completion. Execution of the handler instance can lead to further handler being posted. We say that handler p is posted whenever an instance of p is added to the task buffer. The posting of a handler is done using the asynchronous call mechanism. The interleaving of different picks-and-executes of pending handler instances (a pick-and-execute is often referred to as a dispatch) hides latency in the system. Asynchronous programming has been used to build fast servers and routers [Pai et al. 1999;Kohler et al. 2000], embedded systems and sensor networks [Hill et al. 2000], and forms the basis of web programming using Ajax.

Writing correct asynchronous programs is hard. The loose coupling between asynchronous calls obscures the control and data flow, and makes it harder to reason about them. The programmer must keep track of concurrent interactions, manage data flow between posted handlers (including saving and passing appropriate state between dispatches), and ensure progress. Since the scheduling and resource management is co-operative and performed by the programmer, one mis-behaving procedure (e.g., one that does not terminate, or takes up too many system resources) can bring down the entire system.

We study the problem of algorithmic verification of safety and liveness properties of asynchronous programs. Informally, safety properties specify that “something bad never happens,” and liveness properties specify that “something good eventually happens.” For example, a safety property can state that a web server does not crash while handling a request, and a liveness property can state that (under suitable fairness constraints) every request to a server is eventually served.

For our results, we focus on finite-data asynchronous programs in which data variables range over a finite domain of values. Our main results show that the safety verification for finite-data asynchronous programs is expspace-complete, and the liveness verification problem is decidable and polynomial-time equivalent to Petri net reachability. The finiteness assumption on the data is necessary for decidability results, since all verification questions are already undecidable for 2-counter machines [Minsky 1967]. However, since the depth of the stack or the size of the task buffer could both be unbounded, even with finitely many data values, asynchronous programs define transition systems with possibly infinitely many states.

Specifically, we develop algorithms to check that an asynchronous program (1) reaches a particular data value (global state reachability, to which safety questions can be reduced) and (2) terminates under certain fairness constraints on the scheduler and external events (fair termination, to which liveness questions can be reduced [Vardi 1991]). For fair termination, the fairness conditions on the scheduler rule out certain undesired paths, in which for example the scheduler postpones some pending handler forever.

For sequential programs with synchronous calls, both safety and liveness verification problems have been studied extensively, and decidability results are well known [Sharir and Pnueli 1981;Burkart and Steffen 1994;Reps et al. 1995;Bouajjani et al. 1997;Walukiewicz 2001]. One simple attempt is to reduce reasoning about asynchronous programs to reasoning about synchronous programs by explicitly modeling the task buffer and the scheduling. A way to model an asynchronous program as a sequential one, is to add a counter representing the number of pending instances for each handler, increment the appropriate counter each time a handler is posted, and model the scheduler as a dispatch loop which picks a non-zero counter, decrements it, and executes the corresponding handler code. While the reduction is sound, the resulting system is infinite state, as the counters modeling the pending handler instances can be unbounded, and it is not immediate that existing safety and liveness checkers will be complete in this case (indeed, checking safety and liveness for recursive counter programs is undecidable in general).

Instead, our decidability proofs rely on a connection between asynchronous programs and Petri nets [Reisig 1986], an infinite state concurrency model with many decidable properties. In particular, we show an e

📸 Image Gallery

cover.png

Reference

This content is AI-processed based on open access ArXiv data.

Start searching

Enter keywords to search articles

↑↓
ESC
⌘K Shortcut