Academia.eduAcademia.edu

Communicative action notation with shared storage

A variation of the Communicative Action Notation is presented, in order to allow the sharing of storage among agents. Examples of use of the new notation are given by means of the description of semaphores, monitors and RPC mechanisms. The operational semantics of the new notation is also presented.

Communicative Action Notation with Shared Storage Martin Musicante∗ Peter D. Mosses† UFPE - Departamento de Informatica 50.732-970 Cidade Universitária Recife - PE - Brazil Computer Science Department Aarhus University Ny Munkegade, Bldg. 540 DK-8000 Aarhus C - Denmark [email protected] [email protected] August 1993 Abstract A variation of the Communicative Action Notation is presented, in order to allow the sharing of storage among agents. Examples of use of the new notation are given by means of the description of semaphores, monitors and RPC mechanisms. The operational semantics of the new notation is also presented. Introduction Action Semantics [2] is a formal style of semantics definitions developed to provide “tractable” descriptions of real-life languages. Action Semantics descriptions resemble those written in denotational semantics [1], in the sense that equations defining semantic functions are given to state the meaning of each phrase of a language. Instead of the functional notation used in denotational semantics, a special notation was developed for use in action ∗ † Partly funded by the Brazilian Science Research Council, CNPq, Grant 201.201/91-7. Partly funded by the Danish Science Research Council project DART (5.21.08.03). 1 semantics. This notation is called Action Notation, and it is used in action semantics descriptions very much in the way in which the λ-notation is used in denotational semantics. In Action Semantics, the meaning of each phrase of a language is represented in terms of special entities called actions. Actions can be performed, with various possible outcomes: normal termination (complete), exceptional termination (escape), unsuccessful termination (fail ) or non-termination (diverge). Action Notation provides some basic actions, and actions can be combined in order to obtain complex actions. In [2], combinators corresponding to many control patterns present in programming languages are provided, and further data may be specified ad hoc. Together with action notation, a Data Notation is used to describe semantic entities in action semantics descriptions. A collection of algebraically defined data constructors is provided within the data notation, including numbers, characters, strings, sets, tuples, maps, etc. There exists a third class of entities within the action semantics framework. These entities are called Yielders, and represent data, whose value depends on the current information managed by the enclosing action. Yielders can be evaluated to yield data. An example of a pre-defined yielder is current bindings, which yields the mapping from tokens to bindable data that represents the bindings received by the enclosing action. Action Notation possesses five facets in which each primitive action and action combinator can behave. Basic: This facet deals with pure control flow, without reference to information processing issues. Functional: This facet deals with transient data, which is given to or by an action. For example, when the basic action give the given natural is given a natural number as transient data, it completes, giving the received natural number as a transient as well. The compound action A1 then A2 performs the action A1 first. All transient data produced by A1 is supplied to A2 , which is performed after A1 completes. Declarative: This facet deals with the manipulation of scoped information, represented by associations of tokens to bindable data. For example, the basic action bind “max-length” to 256 completes its performance, 2 producing a binding of the token “max-length” to the natural number 256. Imperative: Storage handling primitives are provided by this facet. A storage, in the action notation context, is a mapping from cells to storable data. For example, the action allocate a cell then store 26 in the given cell . allocates a new cell of the storage, and stores a value in it. This action combines features of both the functional and imperative facets. (Vertical bars are used to enforce the intended grouping of actions, as an alternative to parentheses.) Communicative: This facet provides a system of agents, whose task is to perform actions. Agents can communicate using asynchronous message passing. Each agent has its own communication buffer, in which all the messages sent to the agent are placed. Communication is reliable, in the sense that no message can be lost during transmission; however, there is no bound to the amount of time taken for a message to arrive to its destination buffer, after transmission. Moreover, each agent is created with its own storage. Encapsulation of actions as data is also provided within action notation. This feature gives a simple way to support the description of procedure and function abstractions in programming languages. For example, the performance of the action give abstraction of allocate a cell then store 26 in the given cell . completes, giving an abstraction as a transient. An abstraction is an item of data which encapsulates an action. Abstractions can be enacted ; this operation results in the performance of the encapsulated action. Both transients and bindings can be supplied to the enacted action. For a more detailed description of action notation, the reader can refer to [2] or [5] (the former covers all the aspects related with the formal system; the 3 latter does not cover the communicative facet nor the operational semantics of the notation). A variation on the usual communicative action notation is presented in this work. Our purpose is to introduce storage sharing capabilities to the agents. The modifications proposed here make it possible to write more succinct and readable specifications of concurrent programming concepts than in the original notation, since it is no longer necessary to represent shared storage by auxiliary agents and communication protocols. Our variation on the communicative action notation does not add any new primitive actions or combinators to the original action notation. It provides the notion of storage shared among several agents merely by introducing a new sort of contracts (the sharing ones), and the corresponding extension of the offer action. A couple of new yielders are introduced as well. Then some conclusions are stated. An appendix provides the operational semantics of the entire extended action notation, and can be compared to [2, App. C]. Some examples of use of the new notation are given. The last section of this work provides the operational semantics of the new notation. 1 Communicative Action Notation In what follows, the shared storage variation of the communicative action notation is presented. It includes the same set of actions present in the original communicative action notation. Some new yielders are introduced. The reader may like to compare this section with [2, Chapter 9]. See also [2] for an explanation of the meta-notation used below for specifying functionalities of operations, etc. 1.1 Actions The communicative action notation is based on four primitive actions, whose purpose is to provide the way of defining a system of agents performing actions. Agents can communicate which each other by means of a simple communication mechanism based on asynchronous message passing. • send :: yielder → primitive-action . 4 • remove :: yielder → primitive-action . • offer :: yielder → primitive-action . • patiently :: action → primitive-action . The primitive operation send Y takes a yielder Y that evaluates to a sort of messages (for instance a message[to a:agent][containing d :data]), specializes the sort to an individual message by determining the identification of the sending agent and a serial number, and sends it. The message is assumed to reach the communication buffer of its destination agent in a finite (but unbounded) time. The primitive action remove Y takes an individual message (yielder) Y and eliminates the message from the current communication buffer, failing if it was not there. The action combinator patiently A keeps on trying to perform the action A as long as it fails. Each performance of the argument action A is indivisible. The action offer Y , where Y yields a sort of contract, agrees on the contract with an agent W . The agent will perform the action corresponding to an abstraction A. The usual forms of Y are: a contract [to W ][containing A] a sharing contract [to W ][containing A] The first case corresponds to the original notation. The second case simply adds storage sharing capabilities to the newly contracted agent, which will adopt the storage of the contracting agent as its own. 1.2 Yielders To the already existing current buffer, performing-agent and contracting-agent, yielders to get the set of sharing agents of the current storage and to get the next serial number of the agent configuration are added here. • current buffer: yielder [of a list [of message]] . • performing-agent, contracting-agent: yielder [of an agent] . 5 • next serial-number: yielder [of a natural] . • sharing-agents: yielder [of a set [of agent]] . The yielder sharing-agents evaluates to the set of those agents which share the storage together with the performing agent. The yielder next serial-number yields the natural giving the serial number of the next communication of the agent. Actually, the addition of this yielder is orthogonal to our storage sharing modification to the notation, and it is only used to provide a cheap form of validation of a message, at the sender’s side, as illustrated in section 2.1.1. 1.3 Data Our only modification to the data related to the communicative facet in action notation is the inclusion of a new sort of contracts, the sharing ones. All the operations already defined on contracts are valid for sharing contracts. Notice that the sorts of contracts and sharing contracts need to be disjoint, in order to avoid ambiguous semantics for the offer action. • agent • user-agent • buffer ≤ distinct-datum . : agent . = list [message] . • communication ≤ distinct-datum . • communication = message | contract | sharing contract (disjoint) . • sharing :: contract → sharing contract (total ) . • sendable = abstraction | agent | ✷ . • contents :: message → sendable (total ), contract → abstraction (total ), sharing contract → abstraction (total ) . 6 • sender :: communication → agent (total ) . • receiver :: communication → agent . • serial :: communication → natural (total) . • [containing ] :: message, sendable → message (partial ), contract, abstraction → contract (partial ), sharing contract, abstraction → sharing contract (partial ) . • [from ] :: communication, agent → communication (partial ) . • [to ] :: message, agent → message (partial ), contract, agent → contract, sharing contract, agent → sharing contract • [at ] :: communication, natural → communication (partial ) . 1.4 Facets/Outcomes • committing ≥ communicating . 1.5 Facets/Incomes • income = ✷ | current buffer | next serial-number | ✷ . 2 Examples Some examples of use of the new notation are now presented. First of all, we consider the description of a Sun-style, serial RPC mechanism. This first example does not exploit sharing. It shows that a description written in the original notation does not need any changes to be adapted to the new one. The serial RPC example helps also to understand the next one: the description of a Sun-style, concurrent RPC mechanism. A third example shows how to describe general semaphores within the new notation. A fourth, and last example shows how to represent monitors. 7 2.1 Serial RPC A description of a Sun-style Remote Procedure Call (RPC) system using the modified communicative action notation is given in this section. The RPC concept is a generalization of the procedure call concept found in most imperative programming languages. The main idea is that the called procedure is performed in a different environment than the calling one. The only communication between the caller and callee procedures is done by means of arguments and results. The Sun RPC model, as described in [4], proposes the existence of remote programs, consisting of versions and procedures within these versions. A remote program is also called a server, and each procedure constitutes a service. Each program version can be registered using a port mapper server, present in each host, at a pre-defined address (port). The port mapper is also an RPC program. For simplicity reasons, we omit the notion of version in the following description, since it does not add any theoretical difficulty, but imposes some syntactic complications. In the serial RPC scheme, the invocation of the remote procedures is made in a serial manner, allowing only one procedure to be executed at a time, by a unique caller. The solution here is, in essence, the same as proposed in [3]. Since only one remote procedure within a program can be performed at a time, there is no need to use the storage sharing allowed by the notation. As it is described in [4], a remote procedure is determined by giving the port in which the enclosing program and version can be localized, together with its own, unique identification within its program and version. Program (and version) names, as well as procedure names are represented in our description by means of syntactic tokens. The port notion, present in the Sun description, is represented by the agent notion in the action notation. 2.1.1 Actions introduces: program-lD , service-lD , port , establish serial RPC with rpc-call . • program-lD ≤ token . 8 , • service-lD ≤ token . • port ≤ agent . • establish serial-RPC with • rpc-call :: yielder[of map [service-lD to abstraction]] → action [communicating] [giving a port] . :: (yielder[of port], yielder[of service-lD], yielder[of data]) → action [giving a tuple | diverging | communicating] [using next serial-number] . privately introduces: rpc-reply , stub-action , perform service using . :: (yielder[of message], yielder[of tuple]) → action [communicating] . • stub-action : action . • perform service using ::yielder[of message[containing (service-lD, tuple)]] → action . • rpc-reply The performance of the action establish serial-RPC with M establishes an RPC server whose remote procedures are given by the map M . Each abstraction in the range of the map M , when enacted, can use a given value (the parameter to the remote call) and should give a value, the result, or escape. Note that, as it happens with the existing implementation, ports are allocated when the server is started. The agent identified with the service will perform the stub-action, explained below. (1) establish serial RPC with M :yielder = bind “services” to the map [token to abstraction] yielded by M before subordinate a port then give it and send a message[to it] [containing closure of abstraction of stub-action] . The action rpc-call(A, S, V ) is used in order to call the remote procedure identified with the service-ID S, at the remote program A. The tuple V represents the arguments to the remote procedure. The caller agent waits until the service is (eventually) done. 9 (2) rpc-call (A:yielder[of agent], S:yielder[of service-lD, V :yielder[of tuple]) = indivisibly give next serial-number and then send a message [to A] [containing (S, V )] then receive a message[from A] [containing (the given natural, a tuple)] then give rest of contents of the given message . (3) rpc-reply (M :yielder[of message], V :yielder[of tuple]) = send a message[containing (serial of M , V )] [to the sender of M ] . Notice that we are supposing the existence of the next serial-number yielder. This is a selector over the local state, giving the serial number of the next communication from the performing agent. It is a selector very much in the style of contracting-agent, which yields the agent which originated the performing one, or of current-storage, which yields to a map (from cells to values), representing a snapshot of the storage at the moment. The inclusion of this yielder allows a very simple way of message validation at the caller agent, without any complication to the operational semantics of the action notation. The stub-action receives each remote procedure call, performs a simple validation and then serves it. (4) stub-action = unfolding receive a message[containing a (service-lD, tuple)] then check it is a valid call and then give it and perform service using the given message then rpc-reply(the given message#1, the rest of the given tuple) or check not (the given message is a valid call) and then rpc-reply (the given message, error “invalid remote call”) and then unfold . (5) perform service using M :yielder[of message] = give the tuple yielded by the contents of M then enact application of the abstraction yielded by (the map bound to “services” at the given service-lD#1) to the rest of the given tuple trap give error “procedure failure” . 10 2.1.2 • Yielders is a valid call :: yielder [of message] → yielder [of truth-value] . M :yielder is a valid call = component#1 of contents of M is in mapped-set (the map bound to “services”) . (1) 2.1.3 Data introduces: error • error , error-value , void-value . :: string → error-value (total ) . error-value ≤ datum . (1) • void-value = () • token = string . 2.2 Concurrent RPC An RPC scheme is presented here that permits the concurrent execution of several remote procedure calls. This example is based on the previous one, in the sense that there exist the notions of program and procedure within a program. The difference is that several remote procedure calls (possibly to the same procedure) can be served at the same time. All the remote procedures share the same storage. Each procedure is described using a separate agent in the system. In the original version of communicative action notation [2], each agent has a separate storage, and shared storage can be represented by introducing auxiliary agents and communication protocols for allocating, storing in, and inspecting the local storage cells of these agents. A simpler description is obtained here, by using our extended Action Notation. As in the previous example, we identify the notion of port, in the Sun RPC terminology, with the action semantics notion of agent. Once more, each remote procedure within a program can be identified using a syntactic token. 11 Figure 1 shows an example of a concurrent RPC system containing several remote procedures. The arrows exemplify a possible flow of the messages used to represent the RPC protocol. Figure 1: A client and a remote program with two procedures. There exists an RPC stub agent (the one identified with the RPC program port). This agent will receive the remote procedure call messages from any caller agent and, after a validation, forward the call message to the corresponding procedure agent. In the case of failure of the validation, an error reply is sent to the caller agent. Once the agent corresponding to a service receives a call message, it attempts to perform the required service and, then, sends a reply directly to the caller agent (which is blocked, waiting for the answer). The service can possibly yield an error state, in which case an error message is sent to the caller agent. 2.2.1 Actions introduces: port , program-lD , service-lD , establish RPC service with rpc-call . • program-lD ≤ token . • port ≤ agent . 12 , • service-lD ≤ token . • establish concurrent-RPC with :: yielder [of map [service-lD to abstraction] → action [communicating] [giving a port] . • rpc-call :: (yielder[of port], yielder[of service-lD], yielder[of data]) → action [giving a tuple | diverging | communicating] [using next serial-number] . privately introduces: rpc-reply , initiate sharing services of , serverstub-action, service-stub-action, perform service using . • rpc-reply :: (yielder[of message], yielder[of tuple]) → action [communicating] . • server-stub-action : action • initiatesharing services of :: map [service-lD to abstraction] → action [giving a map [service-lD to agent]] . • service-stub-action: action[using a given abstraction | current bindings | current storage][communicating | binding | storing] . • perform service using :: yielder[of message] → action . Establishing an RPC service is done in the same way as in the serial case of the previous example. (1) establish concurrent-RPC with M :yielder[of a map] = subordinate a port then give it and send a message[to the given port][containing application of closure abstraction of server-stub-action to M ] . An RPC call is performed by sending a call message to the agent representing the remote program and, then, waiting for an answer to the message. Note that, in this case, arbitrary calls are allowed, including recursive remote calls. 13 The use of the next serial-number yielder here gives an elegant and natural way of call validation at the caller’s side. In this way, several calls can be made from the same agent without risk of confusion between the various results. (2) rpc-call (A, S, V ) = indivisibly give next serial-number and then send a message [to A] [containing (S, V )] then receive a message[from A [containing (the given natural, a tuple)] then give rest of contents of the given message . (3) rpc-reply (M , V ) = send a message[containing (serial of M , V )] [to the sender of M ] . The action that defines the remote program stub is a loop. It receives, validates and forwards remote call messages to the procedure agents, and sends an error reply message, in the case the call fails to validation. As all the remote procedure agents share their storage with the stub agent, there is room in this action to introduce some global initializations. This was not considered here for simplicity reasons. (4) server-stub-action = initiate sharing services of the given map [service-lD to abstraction] then bind “services” to the given map [service-lD to agent] before unfolding receive a message[containing a (service-lD, tuple)] then check (the given message is a valid call) and then give it and perform service using the given message then rpc-reply(the given message#1, the rest of the given tuple) or check not (the given message is a valid call) and then rpc-reply (the given message, error “invalid remote call”) and unfold . (5) perform service using M :yielder[of message] = give the map bound to “services” at component#1 of contents of M then send a message[containing M ] [to the given agent] . 14 The initiate sharing services M action takes a map from each service-ID in the program to an abstraction, the body of the corresponding remote procedure. The action allocates a sharing agent for each service and gives a map from service-ID to the new contracted agents, as a transient. (6) initiate sharing services of empty-map = give empty-map . (7) initiate sharing services of disjoint-union (m1 :map, m2 :map) = initiate sharing services of m1 and initiate sharing services of m2 then give disjoint-union of the given map2 . (8) initiate sharing services of map (T : service-lD) to (A: abstraction) = offer a sharing contract [to an agent][containing abstraction of subordinate-action] and then receive a message [containing an agent] then give the contents of it then give (map T to the given agent) and send a message[to the given agent] [containing application of closure abstraction of service-stubaction to A] . The following action is used in order to add the call attention and reply features to each abstraction denoting a service body. The service-stub-action receives the service abstraction as a transient. It implements an infinite loop (provided the service does not fail, or perhaps escape). The performance of the service is preceded by the reception of the call message, containing the arguments to the remote procedure. After the service is honored, a reply message is sent to the caller agent, containing the results of the procedure. 15 service-stub-action = bind “service-abstraction” to the given abstraction before unfolding receive a message [from the contracting-agentl [containing a message] then give contents of the given message then regive and enact application of the abstraction bound to “serviceabstraction” to rest of contents of the given message then rpc-reply (the given message#1, the rest of the given tuple) trap rpc-reply (the given message#1, error “procedure failure”) and unfold . (9) The following sections are similar to those of the previous examples, and do not deserve further explanation. 2.2.2 Data introduces: error , error-value, void-value . • error :: string → error-value (total ) . error-value ≤ datum . (1) • void-value = () . 2.2.3 • (1) 2.3 Yielders is a valid call :: message → yielder [of truth-value] . M is a valid call = component#1 of contents of M is in mapped-set(the map bound to “services”) . Semaphores General semaphore operations are provided in this section. Operations P( ) and V( ) are supposed to be used by a number of agents, possibly sharing storage. Though this example does not directly exploit the storage sharing allowed by the notation, it provides a discipline for its use. 16 2.3.1 Actions introduces: semaphore, setup with value , P( ) , V( ). • semaphore ≤ agent . • setup with value :: yielder[of semaphore], yielder[of natural] → action [giving a semaphore | communicating] . • P( ) :: yielder [of semaphore] → action [communicating] . • V( ) :: yielder [of semaphore] → action [communicating] [using current buffer] . privately introduces: semaphore-action . • semaphore-action: action [using a given natural | current storage | current bindings | current buffer] [communicating | storing | binding] . The action setup S with value N is performed in order to create a semaphore of sort S. The natural number N represents the initial value of the semaphore. The action gives the individual semaphore agent as a transient. (1) setup S with value N = subordinate an (S & semaphore) then give the given semaphore and send a message [to the given semaphore] [containing application of closure of abstraction of semaphore-action to N ] . The P operation on the semaphore is described here by a simple sendreceive pair of actions, while a V operation consists only on sending a Vmessage to the semaphore agent. (2) P(S) = give the semaphore yielded by S then send a P-message [to the given semaphore] and then receive a reply-message [from the given semaphore] . 17 V(S) = send a V-message[to the semaphore yielded by S] . The action describing the semaphore behavior consists of a main loop, within which P and V messages are received. A counter (the value of the semaphore) and a queue (of agents waiting for a positive value) are used, in a standard way. (3) (4) semaphore-action = give the given value and allocate a cell then store the given value#1 in the given cell#2 and bind “value” to the given cell#2 and allocate a cell then store empty-queue in it and bind “waiting-queue” to it before unfolding receive a P-message then check (the value stored in the cell bound to “value” is greater than 0) and then decrement “value” and then send a reply-message[to sender of the given message] or check (the value stored in the cell bound to “value” is 0) and then enqueue the given message in “waiting-queue” or receive a V-message and then check is-empty (“waiting-queue”) and then increment “value” or check not is-empty (“waiting-queue”) and then dequeue a message from “waiting-queue” then send a reply-message[to sender of the given message] and then unfold . 2.3.2 Data The data used in this example consists of messages (of three disjoint sorts), stored queues and counters. Queues are represented by lists, and stored in cells bound to tokens. All operations on queues refer to the data being enqueued or dequeued, and the 18 token bound to the cell where the queue is stored. A similar treatment is done to counters. introduces: P-message , V-message , reply-message , queue , empty-queue , enqueue in , dequeue is-empty , increment , decrement . (1) from , message = ✷ | P-message | V-message | reply-message (disjoint) . • token = string . 2.3.2.1 Queue • queue ≤ list . (1) empty-queue = empty-list . • enqueue in :: yielder, token → action [using current bindings] [storing] . • dequeue from :: datum, token → action [using current bindings] [storing | giving a datum] . • is-empty :: token → yielder [of a truth-value] . (2) enqueue D in T = give concatenation(the list stored in the cell bound to T , list of D) then store the given list in the cell bound to T . (3) dequeue D in T = give (D & head of the list stored in the cell bound to T ) and then give tail of the list stored in the cell bound to T then store it in the cell bound to T . (4) is-empty T = the queue stored in the cell bound to T is empty-queue . 2.3.2.2 Counters 19 • increment , decrement :: token → action [storing] [using current bindings | current storage] . (partial) (1) increment T = give successor of the natural stored in the cell bound to T then store it in the cell bound to T . (2) decrement T = give predecessor of the positive-integer stored in the cell bound to T then store it in the cell bound to T . 2.4 Monitors This section is devoted to the formulation of actions establishing monitor primitives. The example presented here is, in many aspects, similar to the concurrent RPC description of section 2.2. Figure 2 shows an example of a monitor containing two procedures. All the agents corresponding to the procedures of the monitor share the same storage. Aside from the procedure agents, two other agents are part of our monitor descriptions: an entry queue manager and a condition queues manager. As in the previous cases, the monitor will be accessible by referring to one of its component agents. In this case, the entry queue manager agent will play the role of the entry port. As in the case of the concurrent RPC scheme, the figure shows a possible flow of messages among agents. The entry manager is similar to the stub agent of section 2.2. The only difference is that it needs the permission of the queues manager to accept each new call to a procedure within the monitor. Also the procedures here are similar to the services of section 2.2. The difference is that they can use the wait and signal operations, and they must send a notification to the queues manager when leaving the monitor. The queues manager is responsible for allowing at most one agent to be active within the monitor at a time. The identification of the procedures of the monitor, as well as the condition queues is done using syntactic tokens. 20 Figure 2: A client agent and a monitor. introduces: monitor, establish a remote monitor with conditions procedures , call , .wait , .signal . and • monitor < agent . • establish a remote monitor with conditions and procedures :: yielder lof set[of token]], yielder [of map [token to abstraction]] → action [giving a monitor] [communicating] . • call • :: (yielder[of monitor], yielder[of token], yielder[of tupel]) → action [communicating] . .wait , .signal :: (yielder[of token], → action [communicating] [using current buffer | current bindings] . privately introduces: entry-manager-action , condition-manager-action , procedure-stub-action , initiate sharing procedures of initiate condition queues using . 21 , • entry-manager-action . • condition-manager-action: action . • procedure-stub-action: action . • initiate sharing procedures of :: map ltoken to abstraction] → action lgiving a map [token to agent]] . • initiate condition queues using :: set [of token] → action [storing | binding] [using current storage] . Establishing a monitor is done in a way that is very similar to that of the previous examples. (1) establish a remote monitor with conditions (S:yielder[of set]) and procedures (M:yielderlof map]) = subordinate an agent then give the given agent and send a message [to the given agent] [containing application of closure abstraction of entry-manager-action to (the set yielded by S, the map yielded by M)] . The call, wait and signal operations are described by a similar pattern of actions as the rpc-call in section 2.2. (2) (call (M :yielder[of message], T :[of service-lD], V :yielder[of tuple]) = indivisibly give next serial-number and send a call-message [to M ] [containing (T , V )] then receive a reply-message[from M ] [containing (the given natural, a tuple)] then give rest of contents of the given message . 22 (3) T :yielder [of token] ⇒ T .wait = indivisibly give next serial-number and send a message [to the agent bound to “condition-manager”] [containing (T )] then receive a reply-message[from the agent bound to “condition -manager”] [containing the given natural] (4) T :yielder [of token] ⇒ T .signal = indivisibly give next serial-number and send a signal-message [to the agent bound to “condition-manager”] [containing (T )] then receive a reply-message[from the agent bound to “condition -manager”][containing the given natural] The entry manager, after the creation of the procedure of the monitor, enters in a loop in which it receives alternatively messages from the clients of the monitor and from the queues manager. No validation of the received messages is made in this case, but it can be added. Some initialization actions can be added to the entry manager, in the same way as discussed in section 2.2. 23 (5) entry-manager-action = subordinate an agent then bound “condition-manager” to it and initiate sharing procedures of the given map#2 then bind “procedures” to the given (map [token to agent]) before send a message [to the agent bound to “condition-manager”] [containing application of closure abstraction of condition-manager-action to the given set #1] and then unfolding receive a message[from any agent] [containing (token, data)] then send a call-message[containing the given message] [to the map bound to “procedures” at component#1 of contents of the given message] and then receive a free-way-message[from the agent bound to “condition-manager”] and then unfold The condition queues manager action is the most complex action in this example. It behaves as a loop, in which three classes of messages are received. When the agent performing this action receives an exit-message, it means that a call is completed, and a new agent can became active within the monitor. 24 (6) condition-manager-action = bind “conditions” to the given set and initiate condition queues union(the given set, set of “waiting-in-monitor”) before unfolding receive an exit-message or receive a waiting-message[containing a token] then enqueue it in contents of it and then check (is-empty “waiting-in-monitor”) and then send a free-way-message[containing nothing] [to contracting -agent] or check not (is-empty “waiting-in-monitor”) and then dequeue a message from “waiting-in-monitor” then send a reply-message [to sender of the given message] [containing serial of it] or receive a signal-message[containing a token] then check is-empty(contents of the given message) and then send a reply-message [to sender of the given message] [containing serial of it] or check not (is-empty(contents of the given message)) and then enqueue the given message in “waiting-in-monitor” and then dequeue a message from the token yielded by contents of the given message then send a reply-message [to the sender of the given message] [containing serial of it] and then unfold . When a wait-message is received, the call is enqueued, and a new agent can became active in the monitor. When a signal-message is received, the signaling agent is enqueued in a queue of waiting agents within the monitor, and an agent of the signaled queue is permitted to became active in the monitor. If there are no agents pending in the signaled queue, the reception is acknowledged, and the signaling agent continues its performance. The condition queues manager uses the same queues actions as in the semaphores 25 example, in order to implement the waiting queues. The following actions are similar to those in section 2.2, and do not deserve any explanation here. (7) initiate sharing procedures of empty-map = give empty-map . (8) initiate sharing procedures of disjoint-union (m1 :map, m2 :map) = initiate sharing procedures of m1 and initiate sharing procedures of m2 then give disjoint-union of the given tuple . (9) initiate sharing procedures of map (T :token) to (A:abstraction) = offer a sharing contract [to an agent][containing abstraction of subordinate-action] and then receive a message [containing an agent] then give map T to contents of the given message and send a message[to contents of the given message] [containing application of closure abstraction of procedure-stubaction to A] . (10) procedure-stub-action = bind “procedure-abstraction” to the given abstraction before unfolding receive a call-message [from the contracting-agent][containing a message] then give contents of the given message then regive and enact application of the abstraction bound to “procedure-abstraction” to the rest of contents of the given message then send-reply (the given message#1, the rest of the given tuple) trap send-reply (the given message#1, error “procedure failure” ) then send an exit-message [to the agent bound to ”condition-manager” and then unfold . (11) initiate condition queues using empty-set = complete . (12) initiate condition queues using union (s1 :set[token], s2 :set[token]) = initiate condition queues using s1 and initiate condition queues using s2 . (13) initiate condition queues using set of (t:token) = 26 allocate a cell then store empty-queue in it and bind t to it . 2.4.1 Data introduces: wait-message , signal-message , call-message , reply-message, free-way-message , error , error-value , void-value , queue , empty-queue , enqueue in , dequeue from , is-empty . (1) message = ✷ | wait-message | signal-message | call-message | replymessage | free-way-message (disjoint) . • token = string . • error :: string → error-value (total ) . (2) error-value ≤ datum . • void-value = () . 2.4.1.1 Queue • queue ≤ list . (1) empty-queue = empty-list . • enqueue in :: datum, token → action [using current bindings] [storing]. • dequeue from :: datum, token → action [using current bindings] [storing | giving a datum] . • is-empty :: token → yielder [of a truth-value] . (2) enqueue D in T = give concatenation(the list stored in the cell bound to T , list of D) then store the given list in the cell bound to T . (3) dequeue D from T = give D& head of the list stored in the cell bound to T and then give tail of the list stored in the cell bound to T then store it in the cell bound to T . (4) is-empty T = the queue stored in the cell bound to T is empty-queue . 27 Conclusions Our work extends the communicative facet of Action Notation, introducing direct sharing storage capabilities among agents. This capability simplifies the description of many concurrent programming concepts, for which the addition of complicated auxiliary actions, agents and data would be necessary, if using the original Action Notation [2]. The examples given in section 2 shows that the new primitives extend in an elegant way the old ones. In the appendix, the operational semantics of the new notation is given. It is a modification of the operational semantics of the original notation in [2, Appendix C]. Acknowledgements Are due to Silvio Meira and Giovanni Lucero Palma for reading and commenting on previous versions of this report. Also to the Brazilian Science Research Council (CNPq) and the Danish Science Research Council, project DART, for funding in part the development of this work. References [1] P.D.Mosses, Denotational Semantics. In J. van Leeuwen, A. Meyer, M. Nivat, M. Paterson, and D. Perrin, editors, Handbook of Theoretical Computer Science, Volume M, chapter 11. Elsevier Science Publishers, Amsterdam; and MIT Press, 1990. [2] P.D.Mosses, Action Semantics. Cambridge University Press, Tracts in Theoretical Computer Science Series No 26, 1992. [3] M.Musicante, The Sun RPC Language Semantics, In Proceedings of: PANEL’92, XVIII Latin-American Conference of Informatics, Las Palmas, Spain, September 1992. [4] Sun Microsystems, RPC: Remote Procedure Call Specification. RFC 1050, 1988. 28 [5] D.A.Watt, Programming Language Syntax and Semantics, Prentice Hall International, 1991. 29 A Operational Semantics This section corresponds to the operational semantics of the variation to the action notation we propose. This section is a modified version of [2, Appendix C], and gives the operational semantics of the whole action notation. A.1 Abstract Syntax The only modification to the abstract syntax given in [2] consists of adding the terminals sharing-agents and next serial-number to the Yielder rule. (1) closed except Data grammar: A.1.1 (1) (2) (3) (4) (5) (6) (7) = Simple-Action | [[Action-Prefix Action]] | [[Action Action-infix Action D]] . Simple-Action = Constant-Action | [[Simple-Prefix Yielder]] | [[To-Prefix Yielder “to” Yielder ]] | [[“store” Yielder “in” Yielder]] . Constant-Action = “complete” | “escape” | “fail” | “commit” | “unfold”. Simple-Prefix = “give” | “choose” | “produce” | “unbind” | “unstore” | “reserve” | “unreserve” | “enact” | “send” | “remove” | “offer” | “undirect” | “inderectly produce” . To-Prefix = “bind” | “indirectly bind” | “redirect” . Action-Prefix = “unfolding” | “indivisibly” | “patiently” . Action-infix = “or’ | “and” | “and then” | “then” | “trap” | “moreover” | “and then moreover” | “then moreover” | “hence” | “thence” | “ before” | “then before” . Action A.1.2 (1) Actions Yielders Yielder = Data-Constant | Abstraction | [[Data-Unary “(|” Yielder “|)”]] | [[Data-Binary a Yielder “(|” Yielder “,” Yielder “|) ]] | 30 [[“if” Yielder “then” Yielder “else” Yielder D]] | [[“the” Data “yielded by” Yielder]] | [[Yielder “receiving” Yielder]] | “them” | “current bindings” | “current storage” | “current buffer” | “current redirections” | “performing agent” | “contracting-agent” | “sharing-agents” | “next serial-number” . Abstraction = Yielder | [[“abstraction of” Action]] | [[Abs-prefix Yielder]] | [[Action-Prefix Abstraction]] | [[Abstraction Action-infix Abstraction]] (2) A.1.3 Data = Data-Constant | [[Data-Unary “(|” Data “|)”]] | [[Data-Binary “(|” Data “,” Data “|)”]] | “if” Data “then” Data “else” Data]] | ✷ Data-Constant = ✷ . Data-Unary =✷. Data-Binary = ✷ . Data (1) (2) (3) (4) A.2 Semantic Entities Several changes are made to the original semantic entities in [2, appendix C], in order to support shared storage among agents: • Commitments are now represented as a pair of updates to the storage and list of communications, instead of the original list of communications. • The storage entity is removed from the local information of an agent, and now appears as shared-info, as a part of the new configuration semantic entity. • a new sort of semantic entities is introduced, to contain the storage updates performed by an agent. includes: Data Notation . includes: Action Notation/∗ /Data(Abstracting for abstraction) . 31 A.2.1 Acting As our proposal does not introduce any new actions, the Acting semantic entities are not changed at all. grammar: (1) Acting (2) Terminated (3) Completed (4) Escaped (5) failed (6) Intermediate = = = = = = (7) Sequencing = (8) (9) Interleaving = Sequencing = (10) Abstracting = A.2.2 Terminated | Intermediate . Completed | Escaped | Failed . h “completed” data bindings i . h “escaped” data i . “failed” . Simple-Action | [[Action-Prefix Acting]] | [[Acting Action-infix Acting]] | hAction datai||hAction bindingsi|| h Action data bindingsi||h “redirect” redirectionsi|| [[Acting (“before” | “then before”) Acting bindings ]] “and then” | “then” | “trap” | “and then moreover” | “then moreover” | “hence” | “thence” | “before” | “then before” . “and” | “moreover” . “and” | “and then” | “then” | “moreover” | “and then moreover” | “then moreover” | “hence” | “thence” | “before” | “then before” . [[“abstraction of’ Acting”]] . Sort Unions The sort-union of a tuple is the sort of all its components. introduces: sort-union . sort-union :: data → datum . (2) sort-union () = nothing . (3) sort-union (d:datum, x:data) = d | sort-union x . (1) 32 A.2.3 States In order to made the same storage shared by several agents, a new component of the state and info semantic entities is introduced. The shared-info represents information that can be consulted and modified not only by the local agent, but possibly by several others. Each time an agent is being advanced, a copy of the current shared storage gets added to its state, as shared information. The storage is no more part of the local information of an agent. introduces: state , local-info , shared-info , info . (1) state = (Acting, local-info, shared-info) . (2) local-info = (redirections, natural, buffer, agent, agent) . (3) shared-info = (storage, set [of agent]) . (4) info = (data, bindings, local-info, shared-info) . A.2.4 Commitments In the original version of action notation, a commitment is either uncommitted, or a list indicating that the agent performed a committing action. This list contains any communications that the action is emitting. An empty list indicates that the agent performed a committing action that does not require communication, like an storage action, or the commit primitive action. In our modification, a commitment is uncommitted or a pair, containing a set (of cells) and a list (of communications). Each time an agent performs a committing action, a commitment will be constructed containing the set of cells modified by the agent and the list of the communications the agent performed, if any. introduces: (1) (2) (3) (4) (5) (6) commitment , committing , committed , commitment of uncommitted, blend , changed cells , items . commitment = committing | uncommitted (disjoint) . committing = (set[cell], list[of communication]) . committed = (empty-set, empty-list) . commitment of c:communication∗ = (empty-set, list of c) . commitment of s:set[cell] = (s, empty-list) . uncommitted: commitment . 33 , (7) (8) blend : commitment2 → commitment (unit is uncommitted) . c1 = (s1 :set[cell], l1 :list[of communication]) c2 = (s2 :set[cell], l2 :list[of communication]) ⇒ (1) (9) c = (s:set[cell], I:list[of communication]) ⇒ (1) (2) (10) (11) blend(c1 , c2 ) = (union(s1 , s2 ), concatenation(l1 , l2 )) . changed cells c = s . items c = items l . updates of uncommitted = empty-set . items of uncommitted = empty-list . A.2.5 Processing A configuration is now representing a snapshot of the system of agents. A configuration entity is formed by the shared information at each instant, and a distributed system of agents, possibly using the shared information. The initial configuration of a system with only one agent (the user-agent,) performing an action A, is also given. introduces: (1) (1) (2) (2) (1) (2) processing, initial-processing , configuration , initial-configuration , agents , event , time , forever , delayed , undelayed , update of . configuration = (shared-info+ , processing) . initial-configuration (A:Acting) = (empty-map, set of user-agent, initial-processing A) . processing = (event delayed time)∗ . initial-processing (A:Acting) = undelayed (A, empty-map, 0, empty-list, user-agent, user-agent) . In order to know the set of all of the present agents in the system, it is enough to search within the shared info, since every agent has its storage there, possibly shared. 34 (3) (1) (2) (3) (4) (1) (2) (3) (4) (5) (6) (7) (8) agents :: shared-info∗ → set [of agent] . agents () = empty-set . initialagents (s:storage, a:set [of agent], i:shared-info∗ ) = union (a, agents i) . event = communication | update | (Acting, local-info) . time = natural | forever . forever: time . delayed :: state, time → processing (total), communication∗ , natural → processing, update, 0 → processing (total ) . (c:communication) delayed (n:natural): processing . (c1 :communication∗ , c2 :communication∗ ) delayed (t ≤ natural) = (c1 delayed t, c2 delayed t) . () delayed (t ≤ time) = () . undelayed e:event = e delayed 0 . An update encapsulates the information needed to update the shared information of an update of (5) A.3 :: (storage, set[cell], agent) → update . Semantic Functions Most of the equations of the original operational semantics were changed. Most of these changes are a simple relocation of the (now shared) storage, introducing the set of the sharing agents as well. A few changes are significant. They deal with the operations related to updating shared storages and to start shared contracts. These modifications are explained in detail below. A.3.1 Data No changes here. introduces: entity , unary-operation 35 , binary-operation . • entity :: Data → Data . (1) (2) (3) entity [[O:Data-Unary “(|” D:Data “|)”]] = unary-operation O (entity D) . entity[[O:Data-Unary “(|” D1 :Data “,” D2 :Data“|)”]] = binary-operation O (entity D1 ) (entity D2 ) . entity [[“if” D1 :Data “then” D2 :Data “else” D3 :Data]] = if (truth-value & entity D1 ) then entity D2 else entity D3 . • Unary-operation • Binary-operation A.3.2 :: Data-Unary, Data → Data . :: Data-Binary, Data, Data → Data . Yielders The only changes in this section are the addition of equations (7)(8) and (7)(9). introduces: evaluated • evaluated (1) (2) (3) (4) (5) (6) (7) :: (Yielder, info) → data . evaluated(Y :Data-constant, i :info) = entity Y . evaluated[[O:Data-Unary, “(|” Y :Yielder “|)”]], i:info) = unary-operation O (evaluated (Y , i)) . evaluated[[O:Data-Binary, “(|” Y1 :Yielder, Y2 :Yielder “|)”]], i:info) = binary-operation O (evaluated (Y1 , i)), (evaluated (Y2 , i)) . evaluated([[“if” Y1 :Yielder “then” Y2 :Yielder “else” Y3 :Yielder]], i:info) = [[“if” (truth-value & evaluated(Y1 , i)) then evaluated(Y2 , i) else evaluated(Y3 , i) . evaluated([[“the” D:Data “yielded by” Y :Yielder]], i:info) = entity D & evaluated(Y , i) evaluated([[Y1 “receiving” Y2 :Yielder]], d:data, b:bindings, x:(local-info, shared-info)) = evaluated(Y1 , d, bindings & evaluated(Y2 , d, b, x), x) . i = (d:data, b:bindings, r:redirections, n:natural, q:buffer, a1 :agent, a2 :agent, s:storage, a:set[agent]) ⇒ 36 (1) (2) (3) (4) (5) (6) (7) (8) (9) (8) (9) (10) (11) (12) (13) evaluated(“them”, i:info) = d . evaluated(“current storage”, i:info) = s . evaluated(“current bindings”, i:info) = b . evaluated(“current buffer”, i:info) = q . evaluated(“current redirections”, i:info) = r . evaluated(“performing-agent”, i:info) = a1 . evaluated(“contracting-agent”, i:info) = a2 . evaluated(“sharing-agents”, i:info) = a . evaluated(“next serial-number”, i:info) = n . evaluated([[“abstraction of” A:Action]], i:info) = [[“abstraction of” A]] evaluated(Y, i) = d:data ⇒ evaluated([[“provision” Y :Yielder]], i:info) = [[“abstraction of” “completed” d empty-map]] . evaluated(Y , i) = b:bindings ⇒ evaluated([[“production” Y :Yielder]], i:info) = [[“abstraction of” “completed” () b ]] . evaluated(Y , i) = r:redirections ⇒ evaluated([[“indirect production” Y :Yielder]], i:info) = [[“abstraction of” “redirect” r]] . evaluated(Y , i) = [[“abstraction of” A:Acting]] ⇒ evaluated([[O:Action-prefix Y :Yielder]], i:info) = [[“abstraction of” [[O A]] ]] . evaluated(Y1 , i) = [[“abstraction of” A1 :Acting]]; evaluated(Y2 , i) = [[“abstraction of” A2 :Acting]] ⇒ evaluated([[Y1 :Yielder O:Action-prefix Y2 :Yielder]], i:info) = [[“abstraction of” [[ A1 OA2 ]] ]] A.3.3 Actions Except for A.3.3.1.4 and A.3.3.1.6, the changes mainly amount to adding the extra component s:shared-info, and removing the old local storage. introduces: run (1) (2) , stepped . run :: state → (Terminated, local-info, shared-info, commitment) . stepped (A, l, s) ≥ (A′ :Intermediate, l′ :local-info, s′ :shared-info, c′ :commitment); run (A′ , l′ , s′ ) ≥ (A′′ :Terminated, l′′ :local-info, s′′ :shared-info, c′′ :commitment) 37 ⇒ run (A:Acting, l:local-info, s:shared-info) ≥ (A”, l”, s”, blend(c”, c’)) . (3) stepped (A, l, s) ≤ (A’:Terminated, l’:local-info, s’:shared-info, c’:commitment) ⇒ run (A:Acting, l:local-info, s:shared-info) ≥ (A’, l’, s’, c’) . • stepped :: state → (state, commitment) . (4) stepped (A:Terminated, l:local-info, s:shared-info) = nothing . A.3.3.1 Simple (1) i = (d:data, b:bindings, l:local-info); evaluated (Y , i) = nothing ⇒ stepped ([[P :Simple-Prefix Y :Yielder]], i:info) = stepped ([[P :To-Prefix Y :Yielder “to” Y2 :Yielder]], i:info) = stepped ([[P :To-Prefix Y1 :Yielder “to” Y :Yielder]], i:info) = stepped ([[ “store” Y :Yielder “in” Y2 :Yielder]], i:info) = stepped ([[ “store” Y1 :Yielder “in” Y :Yielder]], i:info) = (“failed”, l, uncommitted). A.3.3.1.1 Basic (1) (2) (3) (4) (5) stepped (“complete”, d:data, b:bindings, l:local-info, s:shared-info) = (“completed”, (), empty-map, l, s, uncommitted) . stepped (“escape”, d:data, b:bindings, l:local-info, s:shared-info) = (“escaped”, d, l, s, uncommitted) . stepped (“fail”, d:data, b:bindings, l:local-info, s:shared-info) = (“failed”, l, s, uncommitted) . stepped (“commit”, d:data, b:bindings, l:local-info, s:shared-info) = (“completed”, (), empty-map, l, s, committed) . stepped (“unfold”, d:data, b:bindings, l:local-info, s:shared-info) = nothing . A.3.3.1.2 Functional (1) (2) evaluated (Y , d, b, l, s) = d′ : data ⇒ stepped ([[“give” Y :Yielder]], d:data, b:bindings, l:local-info, s:shared-info) = (“completed”, d′ , empty-map, l, s, uncommitted) . evaluated (Y , d, b, l, s) ≥ d′ : data ⇒ stepped ([[“choose” Y :Yielder]], d:data, b:bindings, l:local-info, s:shared-info) ≥ (“completed”, d′ , empty-map, l, s, uncommitted) . A.3.3.1.3 Declarative 38 (1) (2) (3) evaluated (Y1 , d, b, l, s) = t : token; evaluated (Y2 , d, b, l, s) = v bindable ⇒ stepped ([[“bind” Y1 :Yielder “to” Y2 :Yielder]], d:data, b:bindings, x!) = (“completed”, (), map t to v, x, uncommitted) . evaluated (Y , d, b, l, s) = b′ : bindings ⇒ stepped ([[“produce” Y :Yielder]], d:data, b:bindings, l:local-info, s :shared-info) = (“completed”, (), b′ , l, s, uncommitted) . evaluated (Y , d, b, l, s) = t: token ⇒ stepped ([[“unbind” Y :Yielder]], d:data, b:bindings, l:local-info, (“completed”, (), map t to unknown, l, s, uncommitted) . A.3.3.1.4 Imperative A change of storage still causes a commitment, but the cells involved are now important, in order to determine the shared storage when several agents make changes at once. (1) (2) (3) evaluated (Y1 , d, b, l, s) = v : storable; evaluated (Y2 , d, b, l, s) = c : cell; s = (m:storage, a:set[agent]); s′ = (overlay(map c to v, m), a) ⇒ stepped([[“store” Y1 :Yielder “in” Y2 :Yielder]], d:data, b:bindings, l:local-info, s) = if c is in mapped-set of m then (“completed”, (), empty-map, l, s′ , commitment of set(c)) else (“failed”, l, s, uncommitted) . evaluated (Y , d, b, l, s) = c : cell; s = (m:storage, a:set[agent]); s’ = (overlay(map c to uninitialized, m), a) ⇒ stepped ([[“unstore” Y:Yielder]], d:data, b:bindings, l:local-info, s:shared-info) = if c is in mapped-set of m then (“completed”, (), empty-map, l, s′ , commitment of set(c)) else (“failed”, l, s, uncommitted) . evaluated (Y , d, b, l, s) = c : cell; s = (m:storage, a:set[agent]); s’ = (disjoint-union(map c to uninitialized, m), a) ⇒ stepped ([[“reserve” Y :Yielder]], d:data, b:bindings, l:local-info, s:shared-info) = if not (c is in mapped-set of m) 39 (4) then (“completed”, (), empty-map, l, s′ , commitment of set(c)) else (“failed”, l, s, uncommitted) . evaluated (Y , d, b, l, s) = c : cell; s = (m:storage, a:set[agent]); s = ((m omitting set of c), a) ⇒ stepped ([[“unreserve” Y :Yielder]], d:data, b:bindings, l:local-info, s:shared-info) = if c is in mapped-set of m then (“completed”, (), empty-map, l, s′ , commitment of set(c)) else (“failed”, l, s, uncommitted) . A.3.3.1.5 Reflective (1) evaluated (Y , d, b, l, s) = [[“abstraction of” A:Acting]] ⇒ evaluated (stepped ([[“enact” Y :Yielder]], d:data, b:bindings, I:local-info, s:shared-info) = (given (received (A,empty-map), ()), l, s, uncommitted) . A.3.3.1.6 Communicative The only important modification to this section is the addition of the last equation of the section, dealing with establishing the commitment corresponding to shared contracts. The added equation does not differ in essence from the original one for non-sharing contracts. (1) (2) (3) evaluated (Y , d, b, l, s) = m: message; m [from a1 ] [at n] = m’: message; l = (r:redirections, n:natural, q:buffer, a1 :agent, a2 :agent); l’ = (r, successor n, q, a1 , a2 ) ⇒ stepped([[“send” Y :Yielder]], d:data, b:bindings, l:local-info, s:shared-info) = (“completed”, (), empty-map, l’, s, commitment of m’) . evaluated (Y , d, b, l, s) = m : message; l = (r:redirections, n:natural, q:buffer, a1 :agent, a2 :agent); l’ = (r, q omitting set of m, a1 , a2 ) ⇒ stepped([[“remove” Y :Yielder]], d:data, b:bindings, I:local-info, s:shared-info) = if m is in set of items of q then (“completed”, (), empty-map, l’, s, committed) else (“failed”, l, s, uncommitted) . evaluated (Y , d, b, l, s) = c ≤ contract; c [from a1 ][at n] = c’: contract; 40 (4) l = (r:redirections, n:natural, q:buffer, a1 :agent, a2 :agent); l’ = (r, successor n, q, a1 , a2 ) ⇒ stepped([[“offer” Y :Yielder]], d:data, b:bindings, l:local-info, s:shared-info) = (“completed”, (), empty-map, l’, s, commitment of c’) . evaluated (Y , d, b, l, s) = c ≤ shared contract; c [from a1 ][at n] = c’: shared contract; l = (r:redirections, n:natural, q:buffer, a1 :agent, a2 :agent); I’ = (r, successor n, q, a1 , a2 ) ⇒ stepped([[“offer” Y :Yielder]], d:data, b:bindings, l:local-info, s:shared-info) = (“completed”, (), empty-map, l’, s, commitment of c’) . A.3.3.1.7 Directive (1) (2) (3) (4) evaluated (Y1 :Yielder, d, b, l, s) = t: token; evaluated (Y2 :Yielder, d, b, l, s) = v: bindable | unknown; i: indirection [not in mapped-set r]; l = (r:redirections, x!); l’ = (overlay(map i to v, r), x) ⇒ stepped([[“indirectly bind” Y1 “to” Y2 ]], d:data, b:bindings, l:local-info, s:shared-info) = (“completed”, (), map, t to i, l’, s, committed) . evaluated (Y1 :Yielder, d, b, l, s) = t: token; evaluated (Y2 :Yielder, d, b, l, s) = v: bindable | unknown; i = (b at t): indirection; l = (r:redirections, x!); l’ = (overlay(map i to v, r), x) ⇒ stepped([[“indirectly bind” Y1 “to” Y2 ]], d:data, b:bindings, l:local-info, s:shared-info) = (“completed”, (), map, t to i, l’, s, committed) . evaluated (Y , d, b, l, s) = t: token; i = (b at t): indirection; l = (r:redirections, x!); l’ = (r omitting set of i, x) ⇒ stepped([[“indirectly bind” Y :Yielder]], d:data, b:bindings, I:local-info, s:shared-info) = (“completed”, (), empty-map, l’, s, committed) . evaluated (Y , d, b, l, s) = r’: redirections; l:local-info = (r:redirections, x!); I’ = (overlay(r’, r), x) ⇒ stepped([[“indirectly produce” Y :Yielder]], d:data, b:bindings, l:local-info, s:shared-info) = (“completed”, (), empty-map, l’, s, committed) . 41 (5) l = (r:redirections, x!); l’ = (overlay(r’, r), x) ⇒ stepped (“redirect”, r’:redirections, d:data, b:bindings, l:local-info, s:shared-info) = (“completed”, (), empty-map, l’, s, committed) . A.3.3.2 Compound In what follows, the modifications to the stepped equations amount only to considering the new location of the storage, and the related information added with the shared-info. The equations corresponding to simplified, unfolded, given and received are not changed at all (except to correct some minor bugs in the original specification; a full list of corrigenda is available from Peter D. Mosses). introduces: simplified • simplified • unfolded • given , unfolded , given , received . :: Acting → Acting . :: (Action, Action) → Action . :: (Acting, data) → Acting . • received :: (Acting, bindings) → Acting . A.3.3.2.1 Stepping (1) (2) (3) (4) stepped([[“unfolding” A:Action]], d:data, b:bindings, l:local-info, s:shared -info) = (given (received (unfolded (A, [[“unfolding” A]], b), d), l’, s, uncommitted; stepped([[“indivisibly” A:Acting]], I:local-info, s:shared-info) = run (A, l, s) . run (A, l) ≥ (“failed”, l’:local-info, s’:shared-info, c’:commitment) ⇒ stepped ([[“patiently” A:Acting]], l:local-info, s:shared-info) ≥ ([[“patiently” A]], l’, s’, c’) . run (A, l) ≥ (A’:(Completed | Escaped), l’:local-info, s’:shared-info, c’:commitment) . stepped ([[“patiently” A:Acting]], l:local-info, s:shared-info) ≥ (A’, l’, s’, c’) . 42 (5) (6) (7) (8) (9) (10) stepped (A1 , l, s) ≥ (A1 ’:Acting, l’:local-info, s’:shared-info, c’:commitment); [[A1 O A2 ]]: [[Intermediate Sequencing Intermediate]] | [[Intermediate Interleaving (Intermediate | Completed)]] ⇒ stepped ([[A1 O A2 ]], l:local-info, s:shared-info) ≥ simplified([[A′1 O A2 ]], l’, s’, c’). stepped (A2 , l, s) ≥ (A2 ’:Acting, l’:local-info, s’:shared-info, c’:commitment); [[A1 O A2 ]]: [[(Intermediate | Completed) Interleaving (Intermediate)]!] ⇒ stepped ([[A1 O A2 ]], l:local-info, s:shared-info) ≥ simplified([[A1 O A′2 ]], l’, s’, c’). stepped (A1 , l, s) ≥ (A1 ’:Acting, l’:local-info, s’:shared-info, uncommitment); [[A1 O A2 ]]: [[Intermediate “or” Interleaving]!] ⇒ stepped ([[A1 O A2 ]], l:local-info, s:shared-info) ≥ simplified([[A′1 O A2 ]], l’, s’,uncommitment); . stepped (A2 , l, s) ≥ (A2 ’:Acting, l’:local-info, s’:shared-info, uncommitment); [[A1 O A2 ]]: [[Intermediate “or” Intermediate]!] ⇒ stepped ([[A1 O A2 ]], l:local-info, s:shared-info) ≥ simplified([[A1 O A′2 ]], l’, s’,uncommitment); . stepped (A1 , l, s) ≥ (A1 ’:Acting, l’:local-info, s’:shared-info, c’:uncommitment); [[A1 O A2 ]]: [[Intermediate “or” Intermediate]!] ⇒ stepped ([[A1 O A2 ]], l:local-info, s:shared-info) ≥ (A′1 , l’, s’, c’) . stepped (A2 , l, s) ≥ (A2 ’:Acting, l’:local-info, s’:shared-info, c’:uncommitment); [[A1 O A2 ]]: [[Intermediate “or” Intermediate]!] ⇒ stepped ([[A1 O A2 ]], l:local-info, s:shared-info) ≥ (A′2 , l’, s’, c’) . A.3.3.3 Simplifying No changes here. (1) [[A′1 O A2 ]] : [[Failed Sequencing Intermediate | [[(Failed | Escaped) Interleaving (Intermediate | Completed)]] | [[Escaped Normal Intermediate]] | Completed “trap” Intermediate | 43 (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) (15) (16) [[(Completed | Escaped) “or” Intermediate]] ⇒ simplified [[A′1 O A2 ]] = A′1 . [[A′1 O A2 ]] : [[(Intermediate | Completed) Interleaving (Failed | Escaped)]] | [[Intermediate “or” (Completed | Escaped)]] ⇒ simplified [[A′1 O A2 ]] = A′2 . [[A′1 O A2 ]] : [[(Intermediate | Completed) Interleaving (Failed | Escaped)]] | [[Intermediate Action-infix Intermediate]] | Intermediate “or” (Completed | Escaped)]] ⇒ simplified [[A′1 O A′2 ]] = [[A′1 O A′2 ]] . simplified [[“failed” “or” A2 :Intermediate]] = A2 . simplified [[A1 :Intermediate “or” “failed”]] = A1 . [[“completed” d1 :data b1 :bindings “and” “completed” d2 :data b2 :bindings]] = h“completed” (d1 , d2 ) (disjoint-union (b1 , b2 ))i . simplified [[A1 :Completed “and then”A2 :Intermediate]] = [[A1 “and” A2 ]] . simplified [[“completed” d1 :data b1 :bindings “then” A2 :Intermediate]] = [[“completed” () b1 “and” (given (A2 , d1 ))]] . simplified [[A1 :Completed “and then”A2 :Intermediate]] = given (A2 , d1 ) . simplified [[“completed” d1 :data b1 :bindings “moreover” “completed” d2 :data b2 :bindings]] = h“completed” (d1 , d2 ) (overlay (b1 , b2 ))i . simplified [[A1 :Completed “and then moreover”A2 :Intermediate]] = [[A1 “moreover” A2 ]] . simplified [[“completed” d1 :data b1 :bindings “then moreover” A2 :Intermediate]] = [[“completed” () b1 “moreover” (given (A2 , d1 ))]] . simplified [[“completed” d1 :data b1 :bindings “hence” A2 :Intermediate]] = [[“completed” d1 empty-map “and” (received (A2 , b1 ))]] . simplified [[“completed” d1 :data b1 :bindings “thence” A2 :Intermediate]] = given (received (A2 , b1 ) d1 ) . simplified [[“completed” d1 :data b1 :bindings “before” A2 :Intermediate b:bindings]] = [[“completed” d1 b1 “moreover” (received (A2 , overlay (b1 , b)))]] . simplified [[“completed” d1 :data b1 :bindings “then before” A2 :Intermediate b:bindings]] = [[“completed” () b1 “moreover” (given (received (A2 , overlay (b1 , b)), d1 ))]] . A.3.3.4 Unfolding No changes here. 44 (1) (2) (3) (A1 :Simple-Action, A0 :Action) = if A1 is “unfold” then A0 else A1 . unfolded (O:Action-Prefix A1 :Action]], A0 :action) = if O is “unfolding” then [[O A1 ]] else [[O (unfolded (A1 , A0 )) unfolded ([[A1 :Action O:Action-Infix A2 :Action]], A0 :action) = [[(unfolded (A1 , A0 )) O (unfolded (A2 , A0 ))]] . A.3.3.5 Giving No changes here. (1) (2) (3) (4) (5) given (A:Terminated, d:data) = A . A: Simple-Action | [[“unfolding” Action]] | [[“redirect” redirections]] ⇒ given (A, d:data) = (A, d); given (A, b:bindings, d:data) = (A, d, b) . O: “indivisibly” | “patiently” ⇒ given ([[O A:Acting]], d:data) = [[O (given (A, d))]] . O: “or” | “and” | “moreover” | “and then moreover” |“hence” | “before” ⇒ given ([[A1 :Acting O A2 :Acting]], d:data) = [[(given (A1 , d)) O (given (A2 , d))]] O: “then” | “trap” | “then moreover” | “thence” | “then before” ⇒ given ([[A1 :Acting O A2 :Acting]], d:data) = [[(given (A1 , d)) O A2 ]] . A.3.3.6 Receiving No changes here. (1) (2) (3) (4) (5) received (A:Terminated, b:bindings) = A . A: Simple-Action | [[“unfolding” Action]] | [[“redirect” redirections]] ⇒ received (A, b:bindings) = (A, b); received (A, b:bindings, d:data) = (A, d, b) . O: “indivisibly” | “patiently” ⇒ received ([[O A:Acting]], b:bindings) = [[O (received (A, b))]] . O: “or” | “and” | “and then” | “then” | “trap” | “moreover” | “and then moreover” | “then moreover” ⇒ received ([[A1 :Acting O A2 :Acting]], b:bindings) = [[(received (A1 , b)) O (received (A2 , b))]] O: “hence” | “thence” ⇒ 45 received ([[A1 :Acting O A2 :Acting]], b:bindings) = [[(received (A1 , b)) O A2 ]] . O: “before” | “then before” ⇒ received ([[A1 :Acting O A2 :Acting]], b:bindings) = [[(received (A1 , b)) O A2 b]] . (6) A.3.4 Processes The concluded equations were modified, in order to take a configuration as argument, and not simply a processing1 . introduces: conclusion , concluded , advanced . conclusion = “completed” | “escaped” | “failed” . • concluded :: configuration → conclusion . s = (c: conclusion, x!, user-agent, user-agent) ⇒ concluded(s:shared-info+ , undelayed s:state, p:processing) ≥ c . advanced p ≥ p′ :configuration; concluded p′ ≥ c:conclusion ⇒ concluded p:configuration ≥ c . (1) (2) (3) The core of our modifications appear in the equations corresponding to the advanced semantic functions. In the original operational semantics of action notation, each processing semantic entity is advanced by performing all the undelayed communications first, followed by the stepping of each agent, when no more undelayed communications are present. Our modification consists mainly of the introduction of an intermediate step, after advancing the undelayed communications and before stepping each agent. The purpose of this step is to update the global shared storages before advancing each agent. • advanced :: configuration → configuration . The order of events is not relevant. s: shared-info+ ; p1 , p2 : processing; e1 , e2 : (event delayed time) ⇒ advanced(s, p1 , e1 , e2 , p2 ) = advanced(s, p1 , e2 , e1 , p2 ) (4) 1 Recall that a configuration is formed by shared information together with a processing entity. 46 Advancing delayed communications. (5) p = (c:communication delayed t:positive-integer, p′ :processing); s, s′ : shared-info+ ; (s′ , p′′ :processing) ≤ advanced(s, p′ ) ⇒ advanced(s, p) ≥ (s′ , c delayed predecessor t, p′′ ) . Delivering undelayed messages. (6) m: message[to a1 ]; e = (x!, q:buffer, a1 :agent, a2 :agent); e’ = (x, list of(items q, m), a1 :agent, a2 :agent); ⇒ advanced(s:shared-info+ , undelayed m, e:state delayed t:time, p:processing) = advanced(s, e′ delayed t, p) . (7) m:message [to agent[not in set of agents of s]] ⇒ advanced(s:shared-info+ , undelayed m, p:processing) = advanced(s, p) Initiating the execution of contracts. (8) (9) c:contract [to a ≤ agent][from a2 :agent][containing [[“abstraction of” A:Acting]]]; s: shared-info+ ; a1 : a[not in agents of s]; s’ = (s, empty-map, set of a1 ); e = (given (received( A, empty-map), ()), empty-map, 0, empty-list, a1 , a2 ); advanced(s, undelayed c, p:processing) ≥ (advanced(s′ , p), undelayed e) . c:sharing contract [to a ≤ agent][from a2 :agent] [containing [[“abstraction of” A:Acting]]]; s = (s1 :shared-info∗ , (m:storage, a′ :set[agent]), s2 :shared-info∗ ); a2 is in a′ = true; a1 : a[not in agents of s]; e = (given (received( A, empty-map), ()), empty-map, 0, empty-list, a1 , a2 ); 47 (10) (11) s′ = (s1 , m, union(a′ , set of a1 ), s2 ) ⇒ advanced(s, undelayed c, p) ≥ (advanced(s′ , p:processing), undelayed e) . s: shared-info+ ; c: contract[to a ≤ agent] a [not in agents of s] = nothing ⇒ advanced(s, undelayed c, p:processing) ≥ (advanced(s, p), undelayed c) . s: shared-info+ ; c: sharing contract[to a ≤ agent] a [not in agents of s] = nothing ⇒ advanced(s, undelayed c, p:processing) ≥ (advanced(s, p), undelayed c) . Updating storages. (12) sort-union p & undelayed communication = nothing; s = (s1 : shared-info∗ , (m:storage, a:set[agent],) s2 :shared-info∗ ); a1 is in a = true; m2 = disjoint-union (m omitting u, m1 restricted to u); ⇒ advanced (s:shared-info+ , update of (m1 :storage, u:set[cell], a1 :agent), p:processing) = advanced (s1 , m2 , a, s2 , p) Advancing delayed states. (13) (14) sort-union p & undelayed (communication | update) = nothing; s: shared-info∗ ; ⇒ advanced(s, e:state delayed t:time, p:processing) ≥ advanced (s, p), e delayed predecessor t sort-union p & undelayed (communication | update) = nothing; s: shared-info+ ; ⇒ advanced(s, e:state delayed forever, p:processing) ≥ (advanced(s, p), e delayed forever) . Advancing to intermediate states. 48 (15) sort-union p & undelayed (communication | update) = nothing; s = (s1 :shared-info∗ , (m:storage, a:set[agent]), s2 :shared-info∗ ); a1 is in a = true; e = (x!, a1 :agent, a2 :agent); stepped (e, m, a) ≥ (e’:(Intermediate, local-info), m’:storage, a’:set [of agent], c:commitment) ⇒ advanced(s, undelayed e:state, p:processing) ≥ (advanced(s, undelayed update of (m’, changed cells of c, a1 ), items of c delayed natural, p), e’ delayed natural) . Advancing to terminating states. (16) sort-union p & undelayed (communication | update) = nothing; s = (s1 :shared-info∗ , (m:storage, a:set[agent]), s2 :shared-info∗ ); a1 is in a = true; e = (x!, a1 :agent, a2 :agent); stepped (e, m, a) ≥ (e’:(Tntermediate, local-info), m’:storage, a’:set [of agent], c:commitment) ⇒ advanced(s, undelayed e:state, p:processing) ≥ (advanced(s, undelayed update of (m’, changed cells of c, a1 ), items ofc delayed natural, p), e’ delayed forever) . 49 Contents 1 Communicative Action 1.1 Actions . . . . . . . 1.2 Yielders . . . . . . . 1.3 Data . . . . . . . . . 1.4 Facets/Outcomes . . 1.5 Facets/Incomes . . . 2 Examples 2.1 Serial RPC . . . 2.1.1 Actions . 2.1.2 Yielders . 2.1.3 Data . . . 2.2 Concurrent RPC 2.2.1 Actions . 2.2.2 Data . . . 2.2.3 Yielders . 2.3 Semaphores . . . 2.3.1 Actions . 2.3.2 Data . . . 2.4 Monitors . . . . . 2.4.1 Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A Operational Semantics A.1 Abstract Syntax . . . A.1.1 Actions . . . A.1.2 Yielders . . . A.1.3 Data . . . . . A.2 Semantic Entities . . A.2.1 Acting . . . . A.2.2 Sort Unions . A.2.3 States . . . . Notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 4 5 6 7 7 . . . . . . . . . . . . . 7 8 8 11 11 11 12 16 16 16 17 18 20 27 . . . . . . . . 30 30 30 30 31 31 32 32 33 A.2.4 Commitments A.2.5 Processing . . A.3 Semantic Functions . A.3.1 Data . . . . . A.3.2 Yielders . . . A.3.3 Actions . . . A.3.4 Processes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 34 35 35 36 37 46