Mailbox Types for Unordered Interactions

01/12/2018 ∙ by Ugo de'Liguoro, et al. ∙ 0

We propose a type system for reasoning on protocol conformance and deadlock freedom in networks of processes that communicate through unordered mailboxes. We model these networks in the mailbox calculus, a mild extension of the asynchronous π-calculus with first-class mailboxes and selective input. The calculus subsumes the actor model and allows us to analyze networks with dynamic topologies and varying number of processes possibly mixing different concurrency abstractions. Well-typed processes are deadlock free and never fail because of unexpected messages. For a non-trivial class of them, junk freedom is also guaranteed. We illustrate the expressiveness of the calculus and of the type system by encoding instances of non-uniform, concurrent objects, binary sessions extended with joins and forks, and some known actor benchmarks.

READ FULL TEXT VIEW PDF
POST COMMENT

Comments

There are no comments yet.

Authors

page 2

This week in AI

Get the week's most popular data science and artificial intelligence research sent straight to your inbox every Saturday.

1 Introduction

Message passing is a key mechanism used to coordinate concurrent processes. The order in which a process consumes messages may coincide with the order in which they arrive at destination (ordered processing) or may depend on some intrinsic property of the messages themselves, such as their priority, their tag, or the shape of their content (out-of-order or selective processing). Ordered message processing is common in networks of processes connected by point-to-point channels. Out-of-order message processing is common in networks of processes using mailboxes, into which processes concurrently store messages and from which one process selectively receives messages. This communication model is typically found in the various implementations of actors [22, 1] such as Erlang [3], Scala and Akka actors [21], CAF [8] and Kilim [45]. Non-uniform, concurrent objects [43, 41, 14] are also examples of out-of-order message processors. For example, a busy lock postpones the processing of any message until it is released by its current owner. Out-of-order message processing adds further complexity to the challenging task of concurrent and parallel application development: storing a message into the wrong mailbox or at the wrong time, forgetting a message in a mailbox, or relying on the presence of a particular message that is not guaranteed to be found in a mailbox are programming mistakes that are easy to do and hard to detect without adequate support from the language and its development tools.

1class Account(var balance: Double) extends ScalaActor[AnyRef] {
2  private val self = this
3  override def process(msg: AnyRef) {
4    msg match {
5      case dm: DebitMessage =>  \label{begin.account.debit}
6        balance += dm.amount
7        val sender = dm.sender.asInstanceOf[Account]
8        sender.send(ReplyMessage.ONLY)  \label{end.account.debit}
9      case cm: CreditMessage =>  \label{begin.account.credit}
10        balance -= cm.amount
11        val sender    = cm.sender.asInstanceOf[ScalaActor[AnyRef]]
12        val recipient = cm.recipient.asInstanceOf[Account]
13        recipient.send(new DebitMessage(self, cm.amount))  \label{account.credit.send}
14        receive {  \label{account.credit.wait}
15          case rm: ReplyMessage =>
16            sender.send(ReplyMessage.ONLY)
17        }  \label{end.account.credit}
18      case _: StopMessage => exit()  \label{account.stop}
19      case message =>  \label{begin.account.unknown}
20        val ex = new IllegalArgumentException("Unsupportedmessage")
21        ex.printStackTrace(System.err)  \label{end.account.unknown}
22    }
23  }
24}
Listing 1: An example of Scala actor taken from the Savina benchmark suite [27].

The Scala actor in Listing 1, taken from the Savina benchmark suite [27], allows us to illustrate some of the subtle pitfalls that programmers must carefully avoid when dealing with out-of-order message processing. The process method matches messages found in the actor’s mailbox according to their type. If a message of type DebitMessage is found, then balance is incremented by the deposited amount and the actor requesting the operation is notified with a ReplyMessage (lines LABEL:begin.account.debitLABEL:end.account.debit). If a message of type CreditMessage is found, balance is decremented by the amount that is transferred to recipient (lines LABEL:begin.account.creditLABEL:account.credit.send). Since the operation is meant to be atomic, the actor temporarily changes its behavior and waits for a ReplyMessage from recipient signalling that the transfer is complete, before notifying sender in turn (lines LABEL:account.credit.waitLABEL:end.account.credit). A message of type StopMessage terminates the actor (line LABEL:account.stop).

Note how the correct execution of this code depends on some key assumptions:

  • ReplyMessage should be stored in the actor’s mailbox only when the actor is involved in a transaction, or else the message would trigger the “catch all” clause that throws a “unsupported message” exception (lines LABEL:begin.account.unknownLABEL:end.account.unknown).

  • No debit or credit message should be in the actor’s mailbox by the time it receives StopMessage, or else some critical operations affecting the balance would not be performed.

  • Two distinct accounts should not try to simultaneously initiate a transaction with each other. If this were allowed, each account could consume the credit message found in its own mailbox and then deadlock waiting for a reply from the other account (lines LABEL:account.credit.waitLABEL:end.account.credit).

Static analysis techniques that certify the validity of assumptions like these can be valuable for developers. For example, session types [25] have proved to be an effective formalism for the enforcement of communication protocols and have been applied to a variety of programming paradigms and languages [2], including those based on mailbox communications [35, 7, 16, 37]. However, session types are specifically designed to address point-to-point, ordered interactions over channels [23]. Retrofitting them to a substantially different communication model calls for some inevitable compromises on the network topologies that can be addressed and forces programmers to give up some of the flexibility offered by unordered message processing.

Another aspect that complicates the analysis of actor systems is that the pure actor model as it has been originally conceived [22, 1] does not accurately reflect the actual practice of actor programming. In the pure actor model, each actor owns a single mailbox and the only synchronization mechanism is message reception from such mailbox. However, it is a known fact that the implementation of complex coordination protocols in the pure actor model is challenging [47, 46, 28, 9]. These difficulties have led programmers to mix the actor model with different concurrency abstractions [26, 46], to extend actors with controlled forms of synchronization [47] and to consider actors with multiple/first-class mailboxes [20, 28, 9]. In fact, popular implementations of the actor model feature disguised instances of multiple/first-class mailbox usage, even if they are not explicitly presented as such: in Akka, the messages that an actor is unable to process immediately can be temporarily stashed into a different mailbox [20]; in Erlang, hot code swapping implies transferring at runtime the input capability on a mailbox from a piece of code to a different one [3].

In summary, there is still a considerable gap between the scope of available approaches used to analyze mailbox-based communicating systems and the array of features used in programming these systems. To help narrowing this gap, we make the following contributions:

  • We introduce mailbox types, a new kind of behavioral types with a simple and intuitive semantics embodying the unordered nature of mailboxes. Mailbox types allow us to describe mailboxes subject to selective message processing as well as mailboxes concurrently accessed by several processes. Incidentally, mailbox types also provide precise information on the size and reachability of mailboxes that may lead to valuable code optimizations.

  • We develop a mailbox type system for the mailbox calculus, a mild extension of the asynchronous -calculus [44] featuring tagged messages, selective inputs and first-class mailboxes. The mailbox calculus allows us to address a broad range of systems with dynamic topology and varying number of processes possibly using a mixture of concurrency models (including multi-mailbox actors) and abstractions (such as locks and futures).

  • We prove three main properties of well-typed processes: the absence of failures due to unexpected messages (mailbox conformance); the absence of pending activities and messages in irreducible processes (deadlock freedom); for a non-trivial class of processes, the guarantee that every message can be eventually consumed (junk freedom).

  • We illustrate the expressiveness of mailbox types by presenting well-typed encodings of known concurrent objects (locks and futures) and actor benchmarks (atomic transactions and master-workers parallelism) and of binary sessions extended with forks and joins. In discussing these examples, we emphasize the impact of out-of-order message processing and of first-class mailboxes.

Structure of the paper.

We start from the definition of the mailbox calculus and of the properties we expect from well-typed processes (Section 2). We introduce mailbox types (Section 3.1) and dependency graphs (Section 3.2) for tracking mailbox dependencies in processes that use more than one. Then, we present the typing rules (Section 3.3) and the soundness results of the type system (Section 3.4). In the latter part of the paper, we discuss a few more complex examples (Section 4), related work (Section 5) and ideas for further developments (Section 6). Additional definitions and proofs can be found in Appendices AD.

2 The Mailbox Calculus

Table 1: Syntax of the mailbox calculus.

We assume given an infinite set of variables , , an infinite set of mailbox names , , a set of tags and a finite set of process variables . We let , range over variables and mailbox names without distinction. Throughout the paper we write for possibly empty sequences of various entities. For example, stands for a sequence of names and for the corresponding set.

The syntax of the mailbox calculus is shown in Table 1. The term represents the terminated process that performs no action. The term represents a message stored in mailbox . The message has tag and arguments . The term represents the parallel composition of and and represents a restricted mailbox with scope . The term represents the invocation of the process named with parameters . For each process variable we assume that there is a corresponding global process definition of the form . A guarded process is a composition of actions. The action represents the process that fails with an error for having received an unexpected message from mailbox . The action represents the process that deletes the mailbox if it is empty and then continues as . The action represents the process that receives an -tagged message from mailbox then continues as with replaced by the message’s arguments. A compound guard offers all the actions offered by and . We assume that all actions in the same guard refer to the same mailbox . The notions of free and bound names of a process are standard and respectively denoted by and .

The operational semantics of the mailbox calculus is mostly conventional. We use the structural congruence relation defined below to rearrange equivalent processes:

Structural congruence captures the usual commutativity and associativity laws of action and process compositions, with and acting as the respective units. Additionally, the order of mailbox restrictions is irrelevant and the scope of a mailbox may shrink or extend dynamically. The reduction relation is inductively defined by the rules

where denotes the usual capture-avoiding replacement of the variables with the mailbox names . Rule [r-read] models the selective reception of an -tagged message from mailbox , which erases all the other actions of the guard. Rule [r-free] is triggered when the process is ready to delete the empty mailbox and no more messages can be stored in because there are no other processes in the scope of . Rule [r-def] models a process invocation by replacing the process variable with the corresponding definition. Finally, rules [r-par], [r-new] and [r-struct] close reductions under parallel compositions, name restrictions and structural congruence. We write for the reflexive and transitive closure of , we write if not and if for all .

Hereafter, we will occasionally use numbers and conditionals in processes. These and other features can be either encoded or added to the calculus without difficulties.

[lock] In this example we model a lock as a process that waits for messages from a mailbox in which acquisition and release requests are stored. The lock is either free or busy. When in state free, the lock nondeterministically consumes an message from . This message indicates the willingness to acquire the lock by another process and carries a reference to a mailbox into which the lock stores a notification. When in state busy, the lock waits for a message indicating that it is being released:

Note the presence of the guard in the definition of and the lack thereof in . In the former case, the lock manifests the possibility that no process is willing to acquire the lock, in which case it deletes the mailbox and terminates. In the latter case, the lock manifests its expectation to be eventually released by its current owner. Also note that fails if it receives a message. In this way, the lock manifests the fact that it can be released only if it is currently owned by a process. A system where two users and compete for acquiring can be modeled as the process

(1)

where

Note that uses the reference – as opposed to – to release the acquired lock. As we will see in Section 3.3, this is due to the fact that it is this particular reference to the lock’s mailbox – and not itself – that carries the capability to release the lock.

[future variable] A future variable is a one-place buffer that stores the result of an asynchronous computation. The content of the future variable is set once and for all by the producer once the computation completes. This phase is sometimes called resolution of the future variable. After the future variable has been resolved, its content can be retrieved any number of times by the consumers. If a consumer attempts to retrieve the content of the future variable beforehand, the consumer suspends until the variable is resolved. We can model a future variable thus:

The process represents an unresolved future variable, which waits for a message from the producer. Once the variable has been resolved, it behaves as specified by , namely it satisfies an arbitrary number of messages from consumers but it no longer accepts messages.

[bank account] Below we see the process definition corresponding to the actor shown in Listing 1. The structure of the term follows closely that of the Scala code:

The last term of the guarded process, which results in a failure, corresponds to the catch-all clause in Listing 1 and models the fact that a message is not expected to be found in the account’s mailbox unless the account is involved in a transaction. The message is received and handled appropriately in the -guarded term.

We can model a deadlock in the case two distinct bank accounts attempt to initiate a transaction with one another. Indeed, we have

where both and ignore the incoming messages, whence the deadlock.

We now provide operational characterizations of the properties enforced by our typing discipline. We begin with mailbox conformance, namely the property that a process never fails because of unexpected messages. To this aim, we define a process context as a process in which there is a single occurrence of an unguarded hole :

The hole is “unguarded” in the sense that it does not occur prefixed by an action. As usual, we write for the process obtained by replacing the hole in with . Names may be captured by this replacement. A mailbox conformant process never reduces to a state in which the only action of a guard is :

We say that is mailbox conformant if for all and .

Looking at the placement of the actions in earlier examples we can give the following interpretations of mailbox conformance: a lock is never released unless it has been acquired beforehand (Example 1); a future variable is never resolved twice (Example 1); an account will not be notified of a completed transaction (with a message) unless it is involved in an ongoing transaction (Example 1).

We express deadlock freedom as the property that all irreducible residuals of a process are (structurally equivalent to) the terminated process:

We say that is deadlock free if implies .

According to Definition 2, if a deadlock-free process halts we have that: (1) there is no sub-process waiting for a message that is never produced; (2) every mailbox is empty. Clearly, this is not the case for the transaction between and in Example 1.

[deadlock] Below is another example of deadlocking process using from Example 1, obtained by resolving a future variable with the value it does not contain yet:

(2)

Notice that attempting to retrieve the content of a future variable not knowing whether it has been resolved is legal. Indeed, does not fail if a message is present in the future variable’s mailbox before it is resolved. Thus, the deadlocked process above is mailbox conformant but also an instance of undesirable process that will be ruled out by our static analysis technique (cf. Example 3.3). We will need dependency graphs in addition to types to flag this process as ill typed.

A property stronger than deadlock freedom is fair termination. A fairly terminating process is a process whose residuals always have the possibility to terminate. Formally:

We say that is fairly terminating if implies .

An interesting consequence of fair termination is that it implies junk freedom (also known as lock freedom [29, 38]) namely the property that every message can be eventually consumed. Our type system does not guarantee fair termination nor junk freedom in general, but it does so for a non-trivial sub-class of well-typed processes that we characterize later on.

3 A Mailbox Type System

In this section we detail the type system for the mailbox calculus. We start from the syntax and semantics of mailbox types (Section 3.1) and of dependency graphs (Section 3.2), the mechanism we use to track mailbox dependencies. Then we present the typing rules (Section 3.3) and the properties of well-typed processes (Section 3.4).

3.1 Mailbox Types

Table 2: Syntax of mailbox types and patterns.

The syntax of mailbox types and patterns is shown in Table 2. Patterns are commutative regular expressions [11] describing the configurations of messages stored in a mailbox. An atom describes a mailbox containing a single message with tag and arguments of type . We let range over atoms and abbreviate with when is the empty sequence. Compound patterns are built using sum (), product () and exponential (). The constants and respectively describe the empty and the unreliable mailbox. There is no configuration of messages stored in an unreliable mailbox, not even the empty one. We will use the pattern for describing mailboxes from which an unexpected message has been received. Let us look at a few simple examples. The pattern describes a mailbox that contains either an message or a message, but not both, whereas the pattern describes a mailbox that either contains a message or is empty. The pattern describes a mailbox that contains both an message and also a message. Note that and may be equal, in which case the mailbox contains two messages. Finally, the pattern describes a mailbox that contains an arbitrary number (possibly zero) of messages.

A mailbox type consists of a capability (either ? or !) paired with a pattern. The capability specifies whether the pattern describes messages to be received from (?) or stored in (!) the mailbox. Here are some examples: A process using a mailbox of type must store an message into the mailbox, whereas a process using a mailbox of type is guaranteed to receive an message from the mailbox. A process using a mailbox of type may store an message into the mailbox, but is not obliged to do so. A process using a mailbox of type decides whether to store an message or a message in the mailbox, whereas a process using a mailbox of type must be ready to receive both kinds of messages. A process using a mailbox of type is guaranteed to receive both an message and a message and may decide in which order to do so. A process using a mailbox of type must store both and into the mailbox. A process using a mailbox of type decides how many messages to store in the mailbox, whereas a process using a mailbox of type must be prepared to receive an arbitrary number of messages.

To cope with possibly infinite types we interpret the productions in Table 2 coinductively and consider as types the regular trees [12] built using those productions. We require every infinite branch of a type tree to go through infinitely many atoms. This strengthened contractiveness condition allows us to define functions inductively on the structure of patterns, provided that these functions do not recur into argument types (cf. Definitions 2 and 3.3).

The semantics of patterns is given in terms of sets of multisets of atoms. Because patterns include types, the given semantics is parametric in the subtyping relation, which will be defined next:

[subpattern] The configurations of are inductively defined by the following equations, where and range over multisets of atoms and denotes multiset union:

Given a preorder relation on types, we write if implies and for every . We write for .

For example, and . It is easy to see that is a pre-congruence with respect to all the connectives and that it includes all the known laws of commutative Kleene algebra [11]: both and are commutative and associative, is idempotent and has unit , distributes over , it has unit and is absorbed by . Also observe that is related covariantly to , that is implies .

We now define subtyping. As types may be infinite, we resort to coinduction:

[subtyping] We say that is a subtyping relation if implies either

  1. and and , or

  2. and and .

We write for the largest subtyping relation and say that is a subtype of (and a supertype of ) if . We write for , for and for .

Items 1 and 2 respectively correspond to the usual covariant and contravariant rules for channel types with input and output capabilities [40]. For example, because a mailbox of type is more permissive than a mailbox of type . Dually, because a mailbox of type provides stronger guarantees than a mailbox of type . Note that and , to witness the fact that the order in which messages are stored in a mailbox is irrelevant.

Mailbox types whose patterns are in particular relations with the constants and will play special roles, so we introduce some corresponding terminology.

[type and name classification] We say that (a name whose type is) is:

  • relevant if and irrelevant otherwise;

  • reliable if and unreliable otherwise;

  • usable if and unusable otherwise.

A relevant name must be used, whereas an irrelevant name may be discarded because not storing any message in the mailbox it refers to is allowed by its type. All mailbox types with input capability are relevant. A reliable mailbox is one from which no unexpected message has been received. All names with output capability are reliable. A usable name can be used, in the sense that there exists a construct of the mailbox calculus that expects a name with that type. All mailbox types with input capability are usable, but is unreliable. Both and are usable. The former type is also relevant because a process using a mailbox with this type must (eventually) store an message in it. On the contrary, the latter type is irrelevant, since not using the mailbox is a legal way of using it.

Henceforth we assume that all types are usable and that all argument types are also reliable. That is, we ban all types like or and all types like or . Example A.1 in Appendix A.1 discusses the technical motivation for these assumptions.

[lock type] The mailbox used by the lock (Example 1) will have several different types, depending on the viewpoint we take (either the lock itself or one of its users) and on the state of the lock (whether it is free or busy). As we can see from the definition of , a free lock waits for an message which is supposed to carry a reference to another mailbox into which the capability to release the lock is stored. Since the lock is meant to have several concurrent users, it is not possible in general to predict the number of messages in its mailbox. Therefore, the mailbox of a free lock has type

from the viewpoint of the lock itself. When the lock is busy, it expects to find one message in its mailbox, but in general the mailbox will also contain messages corresponding to pending acquisition requests. So, the mailbox of a busy lock has type

indicating that the mailbox contains (or will eventually contain) a single message along with arbitrarily many messages.

Prospective owners of the lock may have references to the lock’s mailbox with type or depending on whether they acquire the lock exactly once (just like and in Example 1) or several times. Other intermediate types are possible in the case of users that acquire the lock a bounded number of times. The current owner of the lock will have a reference to the lock’s mailbox of type . This type is relevant, implying that the owner must eventually release the lock.

3.2 Dependency Graphs

We use dependency graphs for tracking dependencies between mailboxes. Intuitively, there is a dependency between and if either is the argument of a message in mailbox or occurs in the continuation of a process waiting for a message from . Dependency graphs have names as vertices and undirected edges. However, the usual representation of graphs does not account for the fact that mailbox names may be restricted and that the multiplicity of dependencies matters. Therefore, we define dependency graphs using the syntax below:

The term represents the empty graph which has no vertices and no edges. The unordered pair represents the graph made of a single edge connecting the vertices and . The term represents the union of and whereas represents the same graph as except that the vertex is restricted. The usual notions of free and bound names apply to dependency graphs. We write for the free names of .

Table 3: Labelled transitions of dependency graphs.

To define the semantics of a dependency graph we use the labelled transition system of Table 3. A label represents a path connecting with . So, a relation means that and are connected in and describes the residual edges of that have not been used for building the path between and . The paths of are built from the edges of (cf. [g-axiom]) connected by shared vertices (cf. [g-trans]). Restricted names cannot be observed in labels, but they may contribute in building paths in the graph (cf. [g-new]).

[graph acyclicity and entailment] Let be the dependency relation generated by . We say that is acyclic if is irreflexive. We say that entails , written , if .

Note that is commutative, associative and has as unit with respect to (see Appendix A.2). These properties of dependency graphs are key to prove that typing is preserved by structural congruence on processes. Note also that is not idempotent. Indeed, is cyclic whereas is not. The following example motivates the reason why the multiplicity of dependencies is important.

Consider the reduction

and observe that stores two messages in the mailbox , each containing a reference to the mailbox . The two variables and , which were syntactically different in , have been unified into in the reduct, which is deadlocked. Unlike previous examples of deadlocked processes, which resulted from mutual dependencies between different mailboxes, in this case the deadlock is caused by the same dependency arising twice.

3.3 Typing Rules

Table 4: Typing rules.

We use type environments for tracking the type of free names occurring in processes. A type environment is a partial function from names to types written as or . We let and range over type environments, we write for the domain of and for the union of and when . We say that is reliable if so are all the types in its range.

Judgments for processes have the form , meaning that is well typed in and yields the dependency graph . Judgments for guards have the form , meaning that is well typed in . We say that a judgment is well formed if and is acyclic. Each process typing rule has an implicit side condition requiring that its conclusion is well formed. For each global process definition we assume that there is a corresponding global process declaration of the form . We say that the definition is consistent with the corresponding declaration if . Hereafter, all process definitions are assumed to be consistent. We now discuss the typing rules in detail, introducing auxiliary notions and notation as we go along.

Terminated process.

According to the rule [t-done], the terminated process is well typed in the empty type environment and yields no dependencies. This is motivated by the fact that does not use any mailbox. Later on we will introduce a subsumption rule [t-sub] that allows us to type in any type environment with irrelevant names.

Message.

Rule [t-msg] establishes that a message is well typed provided that the mailbox allows the storing of an -tagged message with arguments of type and the types of are indeed . The subsumption rule [t-sub] will make it possible to use arguments whose type is a subtype of the expected ones. A message establishes dependencies between the target mailbox and all of the arguments . We write for the dependency graph and use for the empty graph union.

Process invocation.

The typing rule for a process invocation checks that there exists a global definition for which expects exactly the given number and type of parameters. Again, rule [t-sub] will make it possible to use parameters whose types are subtypes of the expected ones. A process invocation yields the same dependencies as the corresponding process definition, with the appropriate substitutions applied.

Guards.

Guards are used to match the content of a mailbox and possibly retrieve messages from it. According to rule [t-fail], the action matches a mailbox with type , indicating that an unexpected message has been found in the mailbox. The type environment may contain arbitrary associations, since the action causes a runtime error. Rule [t-free] states that the action matches a mailbox with type , indicating that the mailbox is empty. The continuation is well typed in the residual type environment . An input action matches a mailbox with type that guarantees the presence of an -tagged message possibly along with other messages as specified by . The continuation must be well typed in an environment where the mailbox has type , which describes the content of the mailbox after the -tagged message has been removed. Associations for the received arguments are also added to the type environment. A compound guard offers the actions offered by and and therefore matches a mailbox with type , where is the pattern that describes the mailbox matched by . Note that the residual type environment is the same in both branches, indicating that the type of other mailboxes used by the guard cannot depend on that of .

The judgments for guards do not yield any dependency graph. This is compensated by the rule [t-guard], which we describe next.

Guarded processes.

Rule [t-guard] is used to type a guarded process , which matches some mailbox of type and possibly retrieves messages from it. As we have seen while discussing guards, is supposed to be a pattern of the form where each is either , or of the form . However, only the patterns that are in normal form are suitable to be used in this typing rule and the side condition checks that this is indeed the case. We motivate the need of a normal form by means of a simple example.

Suppose that our aim is to type a process that consumes either an message or a message from , whichever of these two messages is matched first in , and then continues as or correspondingly. Suppose also that the type of is with , which allows the rules for guards to successfully type check the process. As we have seen while discussing rule [t-in], and must be typed in an environment where the type of has been updated so as to reflect the fact that the consumed message is no longer in the mailbox. In this particular case, we might be tempted to infer that the type of in is and that the type of in is . Unfortunately, the type does not accurately describe the content of the mailbox after has been consumed because, according to , the message may be accompanied by either a message or by a message, whereas only accounts for the second possibility. Thus, the appropriate pattern to be used for typing this process is , where the fact that may be found after consuming is made explicit. This pattern and are equivalent as they generate exactly the same set of valid configurations. Yet, is in normal form whereas is not. In general the normal form is not unique. For example, also the patterns and are in normal form and equivalent to and can be used for typing processes that consume messages from in different orders or with different priorities.

The first ingredient for defining the notion of pattern normal form is that of pattern residual , which describes the content of a mailbox that initially contains a configuration of messages described by and from which we remove a single message with type :

[pattern residual] The residual of a pattern with respect to an atom , written , is inductively defined by the following equations:

If we take the pattern discussed earlier we have . The pattern residual operator is closely related to Brzozowski’s derivative in a commutative Kleene algebra [4, 24]. Unlike Brzozowski’s derivative, the pattern residual is a partial operator: is defined provided that the are supertypes of all types found in -tagged atoms within . This condition has a natural justification: when choosing the message to remove from a mailbox containing a configuration of messages described by , only the tag of the message – and not the type of its arguments – matters. Thus, faithfully describe the received arguments provided that they are supertypes of all argument types of all -tagged message types in . For example, assuming , we have that is defined whereas is not.

We use the notion of pattern residual to define pattern normal forms:

[pattern normal form] We say that a pattern is in normal form, written , if is derivable by the following axioms and rules:

Essentially, the judgment verifies that is expressed as a sum of , and terms where is (equivalent to) the residual of with respect to .

A guarded process yields all the dependencies between the mailbox being used and the names occurring free in the continuations, because the process will not be able to exercise the capabilities on these names until the message from has been received.

Parallel composition.

Rule [t-par] deals with parallel compositions of the form . This rule accounts for the fact that the same mailbox may be used in both and according to different types. For example, might store an message into and might store a message into . In the type environment for the parallel composition as a whole we must be able to express with a single type the combined usages of in and . This is accomplished by introducing an operator that combines types:

[type combination] We write for the combination of and , where is the partial symmetric operator defined as follows:

Continuing the previous example, we have because storing one message and one message in means storing an overall configuration of messages described by the pattern . When is used for both input and output operations, the combined type of describes the overall balance of the mailbox. For example, we have : if we combine a process that stores an message into with another process that consumes both an message and a message from the same mailbox in some unspecified order, then we end up with a process that consumes a message from .

Notice that is a partial operator in that not all type combinations are defined. It might be tempting to relax in such a way that , so as to represent the fact that the combination of two processes results in an excess of messages that must be consumed by some other process. However, this would mean allowing different processes to consume messages from the same mailbox, which is not safe in general (see Example 3.3). For the same reason, the combination of and is always undefined regardless of and . Operators akin to for the combination of channel types are commonly found in substructural type systems for the (linear) -calculus [44, 38]. Unlike these systems, in our case the combination concerns also the content of a mailbox in addition to the capabilities for accessing it.

Suppose that we extend the type combination operator so that . To see why this extension would be dangerous, consider the process

Overall, this process stores into a combination of messages that matches the pattern and retrieves from the same combination of messages. Apparently, is used in a balanced way. However, there is no guarantee that the message is received by the process at the top and that the message is received by the process at the bottom. In fact, the converse may happen because only the tag of a message – not the type or value of its arguments – is used for matching messages in the mailbox calculus.

We now extend type combination to type environments in the expected way:

[type environment combination] We write for the combination of and , where is the partial operator inductively defined by the equations:

With this machinery in place, rule [t-par] is straightforward to understand and the dependency graph of is simply the union of the dependency graphs of and .

Mailbox restriction.

Rule [t-new] establishes that the process creating a new mailbox with scope is well typed provided that the type of is . This means that every message stored in the mailbox by (a sub-process of) is also consumed by (a sub-process of) . The dependency graph of the process is the same as that of , except that is restricted.

Subsumption.

As we have anticipated earlier in a few occasions, the subsumption rule [t-sub] allows us to rewrite types in the type environment and to introduce associations for irrelevant names. The rule makes use of the following notion of subtyping for type environments:

[subtyping for type environments] We say that is a subtype environment of if , where is the least preorder on type environments such that:

Intuitively, means that provides more capabilities than . For example, since a process that is well typed in the environment stores an message into , which is also a valid behavior in the environment where has more capabilities (it is also possible to store a message into ) and there is an irrelevant name not used by the process.

Rule [t-sub] also allows us to replace the dependency graph yielded by with another one that generates a superset of dependencies. In general, the dependency graph should be kept as small as possible to minimize the possibility of yielding mutual dependencies (see [t-par]). The replacement allowed by [t-sub] is handy for technical reasons, but not necessary. The point is that the residual of a process typically yields fewer dependencies than the process itself, so we use [t-sub] to enforce the invariance of dependency graphs across reductions.

We show the full typing derivation for and defined in Example 1. Our objective is to show the consistency of the global process declarations

where and . In the derivation trees below we rename as and and to resonably fit the derivations within the page limits. We start from the body of , which is simpler, and obtain

\justifies\mathit{x}:\sigma\vdash\mathit{x}% \texttt{?}\mathtt{\color[rgb]{0.3,0,0}release}\texttt{.}\mathsf{\color[rgb]{% 0,0.2,0}FreeLock}{}[\mathit{x}]\using\hyperlink{rule:t-in}{\text{\scriptsize[{% t-in}]}}\]\justifies\mathit{x}:\sigma\vdash\mathit{x}\texttt{?}\mathtt{\color[% rgb]{0.3,0,0}release}\texttt{.}\mathsf{\color[rgb]{0,0.2,0}FreeLock}{}[\mathit% {x}]{}::\emptyset\using\hyperlink{rule:t-guard}{\text{\scriptsize[{t-guard}]}}% \]\justifies\mathit{x}:\tau,\mathit{y}:\rho\vdash\mathit{y}\texttt{!}\mathtt{% \color[rgb]{0.3,0,0}reply}{}[\mathit{x}]\mathbin{\texttt{|}}\mathit{x}\texttt{% ?}\mathtt{\color[rgb]{0.3,0,0}release}\texttt{.}\mathsf{\color[rgb]{0,0.2,0}% Lock}{}[\mathit{x}]{}::\{\mathit{y},\mathit{x}\}\sqcup\emptyset\using% \hyperlink{rule:t-par}{\text{\scriptsize[{t-par}]}} \]

where .

Concerning , the key step is rewriting the pattern of in a normal form that matches the branching structure of the process. To this aim, we use the property and the fact that is absorbing for the product connective:

% \justifies\mathit{x}:\texttt{?}\mathbb{0}\vdash\mathtt{\color[rgb]{0,0,0.6}% fail}\leavevmode\nobreak\ \mathit{x}{}::\emptyset\using\hyperlink{rule:t-guard% }{\text{\scriptsize[{t-guard}]}}\]\using\hyperlink{rule:t-guard}{\text{% \scriptsize[{t-guard}]}}\justifies\mathit{x}:\texttt{?}(\mathtt{\color[rgb]{% 0.3,0,0}release}\cdot\mathbb{0})\vdash\mathit{x}\texttt{?}\mathtt{\color[rgb]{% 0.3,0,0}release}\texttt{.}\mathtt{\color[rgb]{0,0,0.6}fail}\leavevmode\nobreak% \ \mathit{x}\using\hyperlink{rule:t-in}{\text{\scriptsize[{t-in}]}}\]% \justifies\mathit{x}:\texttt{?}(\mathbb{1}+\mathtt{\color[rgb]{0.3,0,0}acquire% }{[}\rho{]}\cdot\mathtt{\color[rgb]{0.3,0,0}acquire}{[}\rho{]}^{\ast}+\mathtt{% \color[rgb]{0.3,0,0}release}\cdot\mathbb{0})\vdash{}\cdots+\mathit{x}\texttt{?% }\mathtt{\color[rgb]{0.3,0,0}release}\texttt{.}\mathtt{\color[rgb]{0,0,0.6}% fail}\leavevmode\nobreak\ \mathit{x}\using\hyperlink{rule:t-branch}{\text{% \scriptsize[{t-branch}]}}\]\justifies\mathit{x}:\texttt{?}(\mathbb{1}+\mathtt{% \color[rgb]{0.3,0,0}acquire}{[}\rho{]}\cdot\mathtt{\color[rgb]{0.3,0,0}acquire% }{[}\rho{]}^{\ast}+\mathtt{\color[rgb]{0.3,0,0}release}\cdot\mathbb{0})\vdash{% }\cdots+\mathit{x}\texttt{?}\mathtt{\color[rgb]{0.3,0,0}release}\texttt{.}% \mathtt{\color[rgb]{0,0,0.6}fail}\leavevmode\nobreak\ \mathit{x}{}::\emptyset% \using\hyperlink{rule:t-guard}{\text{\scriptsize[{t-guard}]}}\]\justifies% \mathit{x}:\tau\vdash\mathtt{\color[rgb]{0,0,0.6}free}\leavevmode\nobreak\ % \mathit{x}\texttt{.}\mathtt{\color[rgb]{0,0,0.6}done}+\cdots+\mathit{x}\texttt% {?}\mathtt{\color[rgb]{0.3,0,0}release}\texttt{.}\mathtt{\color[rgb]{0,0,0.6}% fail}\leavevmode\nobreak\ \mathit{x}{}::\emptyset\using\hyperlink{rule:t-sub}{% \text{\scriptsize[{t-sub}]}} \]

The elided sub-derivation concerns the first two branches of and is as follows:

% \justifies\mathit{x}:\texttt{?}\mathbb{1}\vdash\mathtt{\color[rgb]{0,0,0.6}% free}\leavevmode\nobreak\ \mathit{x}\texttt{.}\mathtt{\color[rgb]{0,0,0.6}done% }\using\hyperlink{rule:t-free}{\text{\scriptsize[{t-free}]}}\]\justifies\mathit{x}:\texttt{?}\mathtt{\color[% rgb]{0.3,0,0}acquire}{[}\rho{]}\cdot\mathtt{\color[rgb]{0.3,0,0}acquire}{[}% \rho{]}^{\ast}\vdash\mathit{x}\texttt{?}\mathtt{\color[rgb]{0.3,0,0}acquire}(% \mathit{y})\texttt{.}\mathsf{\color[rgb]{0,0.2,0}BusyLock}{}[\mathit{x},% \mathit{y}]\using\hyperlink{rule:t-in}{\text{\scriptsize[{t-in}]}}\]\justifies% \mathit{x}:\texttt{?}(\mathbb{1}+\mathtt{\color[rgb]{0.3,0,0}acquire}{[}\rho{]% }\cdot\mathtt{\color[rgb]{0.3,0,0}acquire}{[}\rho{]}^{\ast})\vdash\mathtt{% \color[rgb]{0,0,0.6}free}\leavevmode\nobreak\ \mathit{x}\texttt{.}\mathtt{% \color[rgb]{0,0,0.6}done}+\mathit{x}\texttt{?}\mathtt{\color[rgb]{0.3,0,0}% acquire}(\mathit{y})\texttt{.}\mathsf{\color[rgb]{0,0.2,0}BusyLock}{}[\mathit{% x},\mathit{y}]\using\hyperlink{rule:t-branch}{\text{\scriptsize[{t-branch}]}} \]

The process (1), combining an instance of the lock and the users and , is also well typed. As we will see at the end of Section 3.4, this implies that both and are able to acquire the lock, albeit in some unspecified order.

In this example we show that the process (2) of Example 1 is ill typed. In order to do so, we assume the global process declaration

which can be shown to be consistent with the given definition for . In the derivation below we use the pattern and the types , and :

% \justifies\mathit{f}:\tau,c:\texttt{?}\mathbb{1},\mathit{x}:\mathtt{\color[rgb% ]{0,0,0.6}int}\vdash\mathtt{\color[rgb]{0,0,0.6}free}\leavevmode\nobreak\ c% \texttt{.}\mathit{f}\texttt{!}\mathtt{\color[rgb]{0.3,0,0}put}{}[\mathit{x}]% \using\hyperlink{rule:t-free}{\text{\scriptsize[{t-free}]}}\]\justifies\mathit% {f}:\tau,c:\texttt{?}\mathbb{1},\mathit{x}:\mathtt{\color[rgb]{0,0,0.6}int}% \vdash\mathtt{\color[rgb]{0,0,0.6}free}\leavevmode\nobreak\ c\texttt{.}\mathit% {f}\texttt{!}\mathtt{\color[rgb]{0.3,0,0}put}{}[\mathit{x}]{}::\{c,\mathit{f}% \}\using\hyperlink{rule:t-guard}{\text{\scriptsize[{t-guard}]}}\]\justifies% \mathit{f}:\tau,c:\sigma\vdash c\texttt{?}\mathtt{\color[rgb]{0.3,0,0}reply}(% \mathit{x})\texttt{.}\mathtt{\color[rgb]{0,0,0.6}free}\leavevmode\nobreak\ c% \texttt{.}\mathit{f}\texttt{!}\mathtt{\color[rgb]{0.3,0,0}put}{}[\mathit{x}]% \using\hyperlink{rule:t-in}{\text{\scriptsize[{t-in}]}}\]\justifies\mathit{f}:% \tau,c:\sigma\vdash c\texttt{?}\mathtt{\color[rgb]{0.3,0,0}reply}(\mathit{x})% \texttt{.}\mathtt{\color[rgb]{0,0,0.6}free}\leavevmode\nobreak\ c\texttt{.}% \mathit{f}\texttt{!}\mathtt{\color[rgb]{0.3,0,0}put}{}[\mathit{x}]{}::\{c,% \mathit{f}\}\using\hyperlink{rule:t-guard}{\text{\scriptsize[{t-guard}]}}\]% \justifies\mathit{f}:\texttt{!}(\mathtt{\color[rgb]{0.3,0,0}get}{[}\rho{]}% \cdot\mathtt{\color[rgb]{0.3,0,0}put}{[}\mathtt{\color[rgb]{0,0,0.6}int}{]}),c% :\texttt{?}\mathbb{1}\vdash\mathit{f}\texttt{!}\mathtt{\color[rgb]{0.3,0,0}get% }{}[c]\mathbin{\texttt{|}}c\texttt{?}\mathtt{\color[rgb]{0.3,0,0}reply}(% \mathit{x})\texttt{.}\mathtt{\color[rgb]{0,0,0.6}free}\leavevmode\nobreak\ c% \texttt{.}\mathit{f}\texttt{!}\mathtt{\color[rgb]{0.3,0,0}put}{}[\mathit{x}]{}% ::{-}\using\hyperlink{rule:t-par}{\text{\scriptsize[{t-par}]}}\]\justifies% \mathit{f}:\texttt{!}F,c:\texttt{?}\mathbb{1}\vdash\mathit{f}\texttt{!}\mathtt% {\color[rgb]{0.3,0,0}get}{}[c]\mathbin{\texttt{|}}c\texttt{?}\mathtt{\color[% rgb]{0.3,0,0}reply}(\mathit{x})\texttt{.}\mathtt{\color[rgb]{0,0,0.6}free}% \leavevmode\nobreak\ c\texttt{.}\mathit{f}\texttt{!}\mathtt{\color[rgb]{% 0.3,0,0}put}{}[\mathit{x}]{}::{-}\using\hyperlink{rule:t-sub}{\text{% \scriptsize[{t-sub}]}}\]\justifies\mathit{f}:\texttt{!}F\vdash(\nu c)(\mathit{% f}\texttt{!}\mathtt{\color[rgb]{0.3,0,0}get}{}[c]\mathbin{\texttt{|}}c\texttt{% ?}\mathtt{\color[rgb]{0.3,0,0}reply}(\mathit{x})\texttt{.}\mathtt{\color[rgb]{% 0,0,0.6}free}\leavevmode\nobreak\ c\texttt{.}\mathit{f}\texttt{!}\mathtt{% \color[rgb]{0.3,0,0}put}{}[\mathit{x}]){}::{-}\using\hyperlink{rule:t-new}{% \text{\scriptsize[{t-new}]}} \]

In attempting this derivation we have implicitly extended the typing rules so that names with type do not contribute in generating any significant dependency. The critical point of the derivation is the application of [t-par], where we are composing two parallel processes that yield a circular dependency between and . In the process on the left hand side, the dependency arises because is sent as a reference in a message targeted to . In the process on the right hand side, the dependency arises because there are guards concerning the mailbox that block an output operation on the mailbox .

[non-deterministic choice] Different input actions in the same guard can match messages with the same tag. This feature can be used to encode in the mailbox calculus the non-deterministic choice between and as the process

(3)

provided that for . That is, and must be well typed in the same type environment. Below is the typing derivation for (3)

\justifies\Gamma,a:\texttt{?}\mathbb{1}\vdash\mathtt{% \color[rgb]{0,0,0.6}free}\leavevmode\nobreak\ a\texttt{.}P_{i}{}::\varphi% \using\hyperlink{rule:t-guard}{\text{\scriptsize[{t-guard}]}}\]\justifies% \Gamma,a:\texttt{?}(\mathtt{\color[rgb]{0.3,0,0}m}\cdot\mathbb{1})\vdash a% \texttt{?}\mathtt{\color[rgb]{0.3,0,0}m}\texttt{.}\mathtt{\color[rgb]{0,0,0.6}% free}\leavevmode\nobreak\ a\texttt{.}P_{i}\using\hyperlink{rule:t-in}{\text{% \scriptsize[{t-in}]}},i=1,2\]\justifies\Gamma,a:\texttt{?}(\mathtt{\color[rgb]% {0.3,0,0}m}\cdot\mathbb{1}+\mathtt{\color[rgb]{0.3,0,0}m}\cdot\mathbb{1})% \vdash a\texttt{?}\mathtt{\color[rgb]{0.3,0,0}m}\texttt{.}\mathtt{\color[rgb]{% 0,0,0.6}free}\leavevmode\nobreak\ a\texttt{.}P_{1}+a\texttt{?}\mathtt{\color[% rgb]{0.3,0,0}m}\texttt{.}\mathtt{\color[rgb]{0,0,0.6}free}\leavevmode\nobreak% \ a\texttt{.}P_{2}\using\hyperlink{rule:t-branch}{\text{\scriptsize[{t-branch}% ]}}\]\justifies\Gamma,a:\texttt{?}(\mathtt{\color[rgb]{0.3,0,0}m}\cdot\mathbb{% 1}+\mathtt{\color[rgb]{0.3,0,0}m}\cdot\mathbb{1})\vdash a\texttt{?}\mathtt{% \color[rgb]{0.3,0,0}m}\texttt{.}\mathtt{\color[rgb]{0,0,0.6}free}\leavevmode% \nobreak\ a\texttt{.}P_{1}+a\texttt{?}\mathtt{\color[rgb]{0.3,0,0}m}\texttt{.}% \mathtt{\color[rgb]{0,0,0.6}free}\leavevmode\nobreak\ a\texttt{.}P_{2}{}::% \varphi\using\hyperlink{rule:t-guard}{\text{\scriptsize[{t-guard}]}}\]% \justifies\Gamma,a:\texttt{?}(\mathtt{\color[rgb]{0.3,0,0}m}\cdot\mathbb{1})% \vdash a\texttt{?}\mathtt{\color[rgb]{0.3,0,0}m}\texttt{.}\mathtt{\color[rgb]{% 0,0,0.6}free}\leavevmode\nobreak\ a\texttt{.}P_{1}+a\texttt{?}\mathtt{\color[% rgb]{0.3,0,0}m}\texttt{.}\mathtt{\color[rgb]{0,0,0.6}free}\leavevmode\nobreak% \ a\texttt{.}P_{2}{}::\varphi\using\hyperlink{rule:t-sub}{\text{\scriptsize[{t% -sub}]}}\]\justifies\Gamma,a:\texttt{?}\mathbb{1}\vdash a\texttt{!}\mathtt{% \color[rgb]{0.3,0,0}m}{}\mathbin{\texttt{|}}a\texttt{?}\mathtt{\color[rgb]{% 0.3,0,0}m}\texttt{.}\mathtt{\color[rgb]{0,0,0.6}free}\leavevmode\nobreak\ a% \texttt{.}P_{1}+a\texttt{?}\mathtt{\color[rgb]{0.3,0,0}m}\texttt{.}\mathtt{% \color[rgb]{0,0,0.6}free}\leavevmode\nobreak\ a\texttt{.}P_{2}{}::\varphi% \using\hyperlink{rule:t-par}{\text{\scriptsize[{t-par}]}}\]\justifies\Gamma% \vdash(\nu a)(a\texttt{!}\mathtt{\color[rgb]{0.3,0,0}m}{}\mathbin{\texttt{|}}a% \texttt{?}\mathtt{\color[rgb]{0.3,0,0}m}\texttt{.}\mathtt{\color[rgb]{0,0,0.6}% free}\leavevmode\nobreak\ a\texttt{.}P_{1}+a\texttt{?}\mathtt{\color[rgb]{% 0.3,0,0}m}\texttt{.}\mathtt{\color[rgb]{0,0,0.6}free}\leavevmode\nobreak\ a% \texttt{.}P_{2}){}::(\nu a)\varphi\using\hyperlink{rule:t-new}{\text{% \scriptsize[{t-new}]}} \]

where . The key step is the application of [t-sub], which exploits the idempotency of (in patterns) to rewrite as the equivalent pattern .

3.4 Properties of well-typed processes

In this section we state the main properties enjoyed by well-typed processes. As usual, subject reduction is instrumental for all of the results that follow as it guarantees that typing is preserved by reductions:

If is reliable and and , then .

Interestingly, Theorem 3.4 seems to imply that the types of the mailboxes used by a process do not change. In sharp contrast, other popular behavioral typing disciplines (session types in particular), are characterized by a subject reduction result in which types reduce along with processes. Theorem 3.4 also seems to contradict the observations made earlier concerning the fact that the mailboxes used by a process may have different types (Example 2). The type preservation guarantee assured by Theorem 3.4 can be explained by recalling that the type environment in a judgment already takes into account the overall balance between the messages stored into and consumed from the mailbox used by (see Definition 3.3). In light of this observation, Theorem 3.4 simply asserts that well-typed processes are steady state: they never produce more messages than those that are consumed, nor do they ever try to consume more messages than those that are produced.

A practically relevant consequence of Theorem 3.4 is that, by looking at the type of the mailbox used by a guarded process (rule [t-guard]), it is possible to determine bounds to the number of messages that can be found in the mailbox as waits for a message to receive. In particular, if every configuration of contains at most atoms with tag , then at runtime contains at most -tagged messages. As a special case, a mailbox of type is guaranteed to be empty and can be statically deallocated. Note that the bounds may change after receives a message. For example, a free lock is guaranteed to have no messages in its mailbox, and will have at most one when it is busy (see Example 3.3).

The main result concerns the soundness of the type system, guaranteeing that well-typed (closed) processes are both mailbox conformant and deadlock free:

If , then is mailbox conformant and deadlock free.

Fair termination and junk freedom are not guaranteed by our typing discipline in general. The usual counterexamples include processes that postpone indefinitely the use of a mailbox with a relevant type. For instance, the message in the well-typed process where is never consumed because is never used for an input operation.

Nevertheless, fair termination is guaranteed for the class of finitely unfolding processes:

We say that is finitely unfolding if all maximal reductions of use [r-def] finitely many times. If and is finitely unfolding, then is fairly terminating.

The class of finitely unfolding processes obviously includes all finite processes (those not using process invocations) but also many recursive processes. For example, every process of the form where is closed, well typed and finitely unfolding regardless of the number of messages stored in , hence is fairly terminating and junk free by Theorem 3.4.

4 Examples

In this section we discuss a few more examples that illustrate the expressiveness of the mailbox calculus and of its type system. We consider a variant of the bank account shown in Listing 1 (Section 4.1), the case of master-workers parallelism (Section 4.2) and the encoding of binary sessions extended with forks and joins (Sections 4.3 and 4.4).

1class Account(var balance: Double) extends AkkaActor[AnyRef] {
2  override def process(msg: AnyRef) {
3    msg match {
4      case dm: DebitMessage =>
5        balance += dm.amount
6        sender() ! ReplyMessage.ONLY
7      case cm: CreditMessage =>
8        balance -= cm.amount
9        val recipient = cm.recipient.asInstanceOf[ActorRef]
10        val future = ask(recipient, new DebitMessage(self, cm.amount))  \label{account.future.begin}
11        Await.result(future, Duration.Inf)  \label{account.future.end}
12        sender() ! ReplyMessage.ONLY
13      case _: StopMessage => exit()
14      case message =>
15        val ex = new IllegalArgumentException("Unsupportedmessage")
16        ex.printStackTrace(System.err)
17    }
18  }
19}
Listing 2: An Akka actor using futures from the Savina benchmark suite [27].

4.1 Actors using futures

Many Scala programs combine actors with futures [46]. As an example, Listing 2 shows an alternative version of the Account actor in Akka that differes from Listing 1 in the handling of CreditMessages (lines LABEL:account.future.beginLABEL:account.future.end). The future variable created here is initialized asynchronously with the result of the debit operation invoked on recipient. To make sure that each transaction is atomic, the actor waits for the variable to be resolved (line LABEL:account.future.end) before notifying sender that the operation has been completed.

This version of Account is arguably simpler than the one in Listing 1, if only because the actor has a unique top-level behavior. One way of modeling this implementation of Account in the mailbox calculus is to use , discussed in Example 1. A simpler modeling stems from the observation that future in Listing 2 is used for a one-shot synchronization. A future variable with this property is akin to a mailbox from which the value of the resolved variable is retrieved exactly once. Following this approach we obtain the process below:

Compared to the process in Example 1, here the notification from the account is received from the mailbox , which is created locally during the handling of the message. The rest of the process is the same as before. This definition of and the one in Example 1 can both be shown to be consistent with the declaration

where . In particular, the dependencies between and that originate in this version of are not observable from outside itself.

The use of multiple mailboxes and the interleaving of blocking operations on them may increase the likelyhood of programming mistakes causing mismatched communications and/or deadlocks. However, these errors can be detected by a suitable typing discipline such the one proposed in this paper. Types can also be used to mitigate the runtime overhead resulting from the use of multiple mailboxes. Here, for example, the typing of guarantees that this mailbox is used for receiving a single message and that is empty by the time is performed. A clever compiler can take advantage of this information to statically optimize both the allocation and the deallocation of this mailbox.

4.2 Master-workers parallelism

In this example we model a master process that receives tasks to perform from a client. For each task, the master creates a pool of workers and assigns each worker a share of work. The master waits for all partial results from the workers before sending the final result back to the client and making itself available again. The number of workers may depend on some quantity possibly related to the task to be performed and that is known at runtime only.

Below we define three processes corresponding to the three states in which the master process can be, and we leave unspecified:

The “” form used here can be encoded in the mailbox calculus and is typed similarly to the non-deterministic choice of Example 3.3. These definitions can be shown to be consistent with the following declarations:

The usual implementation of this coordination pattern requires the programmer to keep track of the number of active workers using a counter that is decremented each time a partial result is collected [27]. When the counter reaches zero, the master knows that all the workers have finished their job and notifies the client. In the mailbox calculus, we achieve the same goal by means of a dedicated mailbox from which the partial results are collected: when becomes disposable, it means that no more active workers remain.

4.3 Encoding of binary sessions

Session types [23, 25] have become a popular formalism for the specification and enforcement of structured protocols through static analysis. A session is a private communication channel shared by processes that interact through one of its endpoint. Each endpoint is associated with a session type that specifies the type, direction and order of messages that are supposed to be exchanged through that endpoint. A typical syntax for session types in the case of binary sessions (those connecting exactly two peer processes) is shown below:

A session type describes an endpoint used for receiving a message of type and then according to . Dually, a session type describes an endpoint used for sending a message of type and then according to . An external choice describes an endpoint used for receiving a selection (either or ) and then according to the corresponding continuation (either or ). Dually, an internal choice describes an endpoint used for making a selection and then according to the corresponding continuation. Communication safety and progress of a binary session are guaranteed by the fact that its two endpoints are linear resources typed by dual session types, where the dual of is obtained by swapping inputs with outputs and internal with external choices.

In this example we encode sessions and session types using mailboxes and mailbox types. We encode a session as a non-uniform, concurrent object. The object is “concurrent” because it is accessed concurrently by the two peers of the session. It is “non-uniform” because its interface changes over time, as the session progresses. The object uses a mailbox and its behavior is defined by the equations for shown below, where is the session type according to which it must be used by one of the peers:

To grasp the intuition behind the definition of , it helps to recall that each stage of a session corresponds to an interaction between the two peers, where one process plays the role of “sender” and its peer that of “receiver”. Both peers manifest their willingness to interact by storing a message into the session’s mailbox. The receiver always stores a message, while the sender stores either