Atomicity Policies using Design Patterns Ant´onio Rito Silva, Jo˜ao Pereira, Pedro Sousa and Jos´e Alves Marques INESC/IST Technical University of Lisbon R. Alves Redol no 9, 1000 Lisboa, PORTUGAL Tel: +351-1-3100287, Fax: +351-1-3145843
[email protected]
This paper is an INESC Technical Report, AR/2/96, January, 1996. Abstract This paper describes three object-oriented design patterns - local serialization, global serialization and recovery - and their integration to support atomic objects. The paper emphasizes the policies each pattern offers and the heterogeneity of policies resulting from their integration. The policies supported by the patterns include: pessimistic and optimistic serialization; static, dynamic and hybrid global serialization policies; redo, undo, copy and compensating recovery policies. The patterns emphasize decoupling of solutions to offer flexible, adaptable and extensible compositions. The patterns allow: decoupling the functional part from the concurrent part; decoupling concurrency control from concurrency generation; decoupling object-specific information from generic algorithms of concurrency control and recovery; decoupling global serialization policies from local serialization policies; and decoupling concurrency control from recovery.
Keywords: Concurrency Control. Recovery. Object-Oriented Patterns.
1 Introduction Developing concurrent applications is a non-trivial task and there are different trends on the possible solutions. For instance, the decision on whether concurrency can be abstracted from application functionalities is an open issue. We are developing an approach to the development of concurrent and distributed applications which emphasizes their constructive development [Silva 95b]. The development is supported by architectural solutions which should be incorporated as the process development proceeds. Furthermore, applications are perceived in three levels of abstraction: model, policy and mechanism. Models describe users’ expectations about application or system behavior. Policies define algorithms to support application models. Mechanisms define functionality to be used by policies to implement their algorithms. Concerning concurrency, a possible concurrency control model is the serial model that establishes that concurrent accesses to resources do not interfere, which means that the effects of concurrent execution of activities should be equivalent to the ones resulting from their serial execution. From the user’s point of view, activities are independent since other activities (users) currently executing are not relevant to the results. This model is supported by database systems, either using optimistic or pessimistic policies. Each of these policies can use mechanisms to generate concurrency, e.g. thread mechanisms, and to control it, e.g. mutex mechanisms. To allow a constructive development, our approach enforces adaptability and extensibility such that:
The application functionalities are developed first to validate user functional requirements. Concurrency is incorporated afterwards, using default solutions to validate the behavior of concurrent applications. 1
Solutions for concurrency are extensible and tunable to optimize application-specific issues. This approach requires solutions for two kinds of problems: decoupling and composition. It is necessary to decouple concurrency solutions in terms of minimal reusable elements which, once composed, offer customized solutions for the problems at hand. Moreover, decoupling allows constructive addition of these elements and their dynamic configuration. Composition describes how the minimal elements should be composed. For instance, at the language level, object-oriented composition techniques include inheritance and delegation. However, composition techniques can restrict the potential of decoupling solutions. For instance delegation allow dynamic reconfiguration while inheritance does not. Our approach is based on object-oriented design patterns. Three patterns are proposed which solve most of the problems within the contexts we identified, and which are referred to as the pattern forces. These patterns address the decoupling problem. Note that any composition technique needs decoupled solutions. The rest of this paper is structured as follows. Related work and the forces involved in the development of solutions for concurrency are described in section 2. Sections 3, 4 and 5 describe the three design patterns we propose, as well as the policies they support. The integration of these patterns, defining an Atomic Object, is discussed in section 6. Section 7 describes some implementation details which clarify the boundary between the patterns abstractions and their implementation. Finally, in section 8 conclusions are presented.
2 Related Work and Forces Several approaches to the development of concurrent applications propose the separation of application functionalities part from the concurrent part. These approaches are based on object-oriented methods [Jacobson 92], enrichment of existing languages with new keywords and annotations [Caromel 93, Meyer 93, Lohr 93], class hierarchy and platform mechanisms [Liskov 88, Parrington 88, McCue 92, Fazzolare 93], and definition of new languages and models [Lopes 94, Ciancarini 95, Baquero 95]. Most of these approaches enforce the development of a sequential application first, which should be enriched with concurrency afterwards. For instance in [Jacobson 92], a use-case model describing application functionalities from the users’ perspective, does not consider concurrency issues at this stage. Basically, we follow this approach. Concurrency can be decoupled in concurrency generation and concurrency control. The former manages creation, execution and destruction of activities while the latter is responsible for avoiding undesirable interactions. In [Lohr 93], both generation and control are introduced in a sequential program using declarative annotations which are processed prior to compilation. To improve reusability it is desirable to separate the concurrency control algorithms into two parts: a generic reusable part and an application-specific part. In [Weihl 88] a compatibility matrix between the object’s operations is defined. This matrix defines the allowable interference of operations, e.g. whether two read operations can execute concurrently. Concurrency control should be considered at two levels: local and global. Local concurrency control defines access serialization to objects while the latter is responsible for globally serialize a set of accesses. The integration of both perspectives has some problems which are identified and handled in [Weihl 89, Guerraoui 94]. Weihl groups local atomicity in three categories of global serialization protocols: static, when the global order is defined at transaction creation; dynamic, when the global order is defined during transaction execution; and hybrid, when global order is defined when a transaction finishes. He states that a global serialization order is achieved only if all objects use the same global serialization protocol category. Guerraoui defines O-atomicity as the property objects should have to be independent of global serialization protocols and compatible with any other atomic object. O-atomicity allows: application adaptability (global serialization protocols can freely change) and application heterogeneity (objects implementing different local atomicity can coexist in the application). Due to autonomy requirements, in multidatase systems it is difficult to ensure that all databases use the same global serialization protocol. So, different approaches are proposed which achieve global order in the case of heterogeneous serialization protocols. The global order can be constructed bottom-up, using the execution order at each local database system during a two-phase commit protocol [Pu 88]. Another solution forces the conflict between transactions using a ticket protocol in each local database [Georgakopolous 91]. 2
As described in [Weihl 93], it is necessary to consider recovery when building solutions for concurrency control. The construction of concurrency control solutions should be as independent as possible from recovery solutions. However, designers should be aware of the interdependencies and trade-offs of their integration. Object-oriented patterns are an emerging field of software engineering. A pattern describes classes, objects and their collaborations that offer a solution to a problem in a context. Patterns are more than a solution: they describe when to apply themselves, the elements that make up the design solution, and the trade-offs of applying themselves [Gamma 95]. Due to their level of abstraction, patterns can be classified in architectural, design and idiom. An architectural pattern describes a fundamental structure organization for software systems. A design pattern describes recurring design solutions that emphasize how should object-oriented concepts be applied to achieve extensibility and adaptability. Idiom patterns describe programming language dependent solutions. Patterns for concurrent applications are described in [Lavender 95, Schmidt 95]. The former describes an active object while the latter describes the integration of synchronous and asynchronous tasks. This paper presents a set of design patterns which support different policies for concurrency control and recovery. Two kinds of concurrency control policies are considered: global serialization policies, which define inter-object accesses serialization; and local serialization policies, which describe intra-object serialization policies. Influenced by previous research, the forces these patterns solve are:
Decoupling the functional from the concurrent part, as described in [Lopes 94]. Decoupling serialization from concurrency generation, as described in [Lohr 93]. Decoupling object-specific information from generic concurrency control and recovery algorithms, as described in [Weihl 88]. Decoupling global serialization policies from local serialization policies, as described in [Guerraoui 94]. Decoupling serialization from recovery, as described in [Weihl 93]. Support for heterogeneous global serialization policies, as described in [Pu 88]. In general these patterns should:
Support different policies. Support the coexistence of heterogeneous policies. The next sections describe three patterns and a design resulting from their integration. These descriptions are done using four of the format items in [Gamma 95]: intent, which states what the pattern does; structure, which illustrates the classes in the pattern; participants, which describe the responsibilities of classes and objects; and collaborations, which describe how participants collaborate to accomplish their goals. A new format item was added, policy, which stresses the different policies the pattern supports. In order to describe patterns object’s structure and collaborations the notation of [Booch 94] is used.
3 Local Serialization Pattern The Local Serialization pattern defines intra-object serialization policies. The serialization part is decoupled from the object’s functional part. Object accesses are serialized taking into account current accesses, the object’s state and the operation semantics. Different policies can be supported: pessimistic and optimistic. The former verifies serialization before an access while the latter verifies it afterwards. Object-specific information is isolated. A complete description of the this pattern can be found in [Silva 95a].
3
3.1 Intent The Local Serialization pattern describes several local serialization policies and decouples them from objectspecific semantics.
3.2 Structure and Participants The Booch class diagram in figure 1 illustrates the structure of the Local Serialization pattern:
Controller
ctl Activity Context start() terminate() delay() awake() lock() unlock()
1
Serial Interface
1
m()
obj
1
preControl() postControl() schedule()
1 N
Access am
astat 1 req
1 Access Mode compatible()
m() { a= cons(ACCESS); ctl->preControl(a); obj->m’(); ctl->postControl(a) }
Object
1
1
0-1
m’()
Require guard()
Figure 1: Local Serialization: Structure. The main participants in the Local Serialization pattern are:
Serial Interface. Accesses to objects through this interface are serialized. It is responsible for bounding accesses to Object with control invocations to Controller. It interacts with the concurrency generation policy through delay, awake and terminate operations of the Activity Context object. It constructs Access objects which identify accesses and hold information on the semantics of the operations and object being invoked. Controller. Defines the generic part of the serialization policy. It decides whether an operation may continue, stop or be delayed (return values eCONTINUE, eERROR and eDELAY), and which delayed operations may be re-scheduled. Operations preControl and postControl verify access order. Operation schedule verifies which delayed accesses may be resumed. Well-known policies can be implemented by reusable Controller subclasses. Access. Identifies an access and contains its status. The access status can be: executing, pre-pending, post-pending and terminated (attribute astat with values exec, pre, post and ter). It is associated with an Access Mode object and a Require object. Access Mode. Defines the reusable (it can be used by different accesses) object-specific information of the serialization policy. Operation compatible verifies if two Access Mode objects are compatible, e.g. a write access is not compatible with a read access. A matrix of operation compatibility, which specifies the allowable interference between different operations, is split into the different Access Mode subclasses. Require. Defines the non-reusable (it is operation-specific) object-specific information of the serialization policy. Operation guard verifies if Object can be accessed, based on its current state, e.g. suspend the access because it is not possible to get a value from an empty buffer. Object Require holds the access precondition (operation guard). Activity Context. This object encapsulates the concurrency generation policy, isolating them from the serialization policy. Object. Shared data and behavior are encapsulated by Object. Accesses to this object must be serialized. It offers access operations to be used by Require objects. 4
3.3 Collaborations The Booch interaction diagram in figure 2 illustrates collaborations between objects involved in the Local Serialization pattern. :Serial Interface
:Controller
:Access Mode
:Require
:Activity Context
a=cons(ACCESS)
CREATE ACCESS
preControl(a)
PRE CONTROL
a:Access
VERIFY GUARD
a.req->guard()
FOR ALL acs IN select1 VERIFY COMPABILITY
a.am->compatible(acs.am) insert(a)
IF NOT eERROR
update(exec)
IF eCONTINUE delay(a)
IF eDELAY
handle(a)
IF eERROR
POST CONTROL
postControl(a) FOR ALL acs IN select2 VERIFY COMPABILITY
a.am->compatible(acs.am)
IF NOT eDELAY
update(ter) update(post)
IF eDELAY delay(a)
IF eDELAY
handle(a)
IF eERROR RE-SCHEDULE POLICY
schedule()
FOR ALL acs IN select3 AWAKE DELAYED ACCESSES
awake(acs)
Figure 2: Local Serialization: Collaborations. Three collaboration phases are described: 1. Access Creation. This phase creates the Access object, which identifies the access and holds its status, (initialized to pre). It includes an Access Mode object which defines the semantic compatibility between this access and other accesses. When the access also depends on Object’s state a Require object is included. 2. Pre-Control. This phase serializes the new access. The access mode should be compatible with access modes of other executing accesses (with status exec or post) and the Object must be in a state where the precondition, if any, holds (operation guard of the Require object). When the access can not be serialized operation handle must stop the execution by invoking terminate on the Activity Context object. Controller updates the status of Access objects to reflect the access’ status. Selection select1 represents the set of Access objects for which compatibility will be verified. This set and the behavior of the Controller’s preControl operation are policy dependent. 3. Post-Control. This phase verifies if accesses already done are serializable. Serializability verifications (operation postControl) are similar to those of the previous phase. If an eERROR value is returned the handle operation must be able to recover the object’s state. When an access ends other pending accesses may be re-scheduled (operations schedule and awake). Awake accesses must re-execute preControl and postControl operations if their status are, respectively, pre or post. Selections select2 and select3, which represent sets of Access objects involved in compatibility verification, and operations postControl and schedule are policy dependent.
3.4 Policies The Local Serialization policies define the object execution order based on current accesses, object state and operation-semantics. This pattern supports different kinds of policies and decouples the generic part of the policies from the object-specific part. 5
3.4.1
Generic Part
Local serialization policies can use two different generic algorithms: pessimistic, when the object is expected to have high contention, and optimistic, when the level of contention is expected to be low. These two perspectives are coded in subclasses of Controller.
Pessimistic policies control accesses during pre-control phase by verifying compatibility with other Access objects (e.g. selection select1 can denote a set of Access objects whose status is exec or post). During post-control, pessimistic policies do not verify compatibility, since only serializable accesses were allowed (selection select2 denotes a empty set of Access objects). Optimistic policies do not control access during pre-control phase (selection select1 denotes an empty set of Access objects). Nevertheless, if the accessing Access object has a Require object, the precondition is verified (operation guard). Afterwards, during post-control it is necessary to verify if the access is compatible with accesses that have already terminated (e.g. selection select2 can denote Access objects having ter status). 3.4.2
Object-Specific Part
Object-specific information enriches the generic policy with object-state and operation-semantics information. A reader-writer policy uses the operation-semantics to construct an operation compatibility matrix which is used by the local serialization policies. A consumer-producer policy uses the object’s state to define access pre-conditions. These two policies are coded in subclasses of Access Mode and Require, respectively.
Reader-writer policies. They require definition of two Access Mode subclasses, AMRead and AMWrite, which are combined with read and write accesses. Since the readers-writers policy does not depend on the object’s state, Access objects should not be initialized with Require objects. Note that policy fairness depends on the scheduling policy (operation schedule), e.g. the starvation of writers. Consumer-producer policies. They require definition of two Require subclasses, RConsumer and RProducer, which are combined with consumer and producer accesses. RConsumer objects delay accesses when there are no data to consume while RProducer objects delay accesses if the object buffer is full, a limited-capacity storage is considered.
4 Global Serialization Pattern The Global Serialization pattern defines a global inter-object serialization policy. Different policies can be supported: static, where the serialization order is defined whenever a transaction starts executing in the context of a new context transaction manager; dynamic, where the serialization order is defined as objects are accessed; and hybrid, where the serialization order is defined when a transaction finishes (different from commits). This pattern decouples the global serialization policy from the object’s serialization policy. It allows the constructions of applications using several heterogeneous global serialization policies.
4.1 Intent The Global Serialization pattern describes several policies for global order definition and decouples them from local serialization policies.
4.2 Structure and Participants The Booch class diagram in figure 3 illustrates the structure of the Global Serialization pattern: The main participants in the Global Serialization pattern are: 6
Transaction Manager registerTr() finishTr() prepareTr() commitTr() abortTr() statOrder() dynOrder() hybOrder() cmpOrder()
M
context() finish() 1
1 1
Transaction
N {ordered}
M tr
1
Access
Activity Context start() terminate() delay() awake() lock() unlock()
N
1
m() { a= cons(ACCESS); ctl->preControl(a); obj->m’(); }
N
N
Controller
Transactional Interface
1
m() prepare() commit() abort()
ctl
1
1
{ordered}
preControl() postControl() schedule()
obj 1
prepare(tr) { a= getAccess(tr); ctl->postControl(a); }
Object m’()
Figure 3: Global Serialization: Structure.
Transaction Manager. Defines the execution order of transactions, which establishes the global serialization policy of a set of objects. Several Transaction Manager objects defining different global serialization policies can coexist in the same application. Transaction Manager objects have two kinds of responsibilities: to support a global serialization policy; and to ensure that the orders of transactions execution defined by the different Transaction Manager objects are consistent. Operations statOrder, dynOrder, hybOrder and cmpOrder fulfill the former while the other operations are involved in the global serialization policies consistency verification. Operations statOrder, dynOrder and hybOrder define the global serialization policy, and operation cmpOrder returns the relative order of any pair of transactions. Operation cmpOrder(t1,t2) returns true when, in the global order of execution, transaction t1 comes before transaction t2. It may be the case that two transactions are not serialized by the global serialization policy in which case operation cmpOrder returns unknown. A transaction manager maintains an ordered1 list of Transaction objects. Transaction. Identifies an atomic set of accesses to objects. A transaction should always execute in the context of a Transaction Manager object. This object contains the transaction’s status which can be: executing (exec), preparing (prep), committed (comm) or aborted (abor). It maintains the list of objects accessed. A transaction can execute in the context of different Transaction Manager objects, following in each context its global serialization policy. Transactional Interface. Accesses to Object objects made through this interface are serialized according to the global serialization policy defined by a Transaction Manager object. For consistency reasons, the Transactional Interface object is registered in only one Transaction Manager and, so, it follows a single global serialization policy. Several transactions can concurrently access the same Object. The Transactional Interface is responsible for preceding accesses with a control invocation to the Controller object and, when a transaction finishes, for asking the Controller object whether it can commit or abort. Constructs an Access object which identifies the transaction’s access. Controller. Verifies if local accesses respect the global order of execution (supports a particular policy). It decides whether a transactional access may continue, terminate or be delayed and which delayed transactions may be re-scheduled. Maintains an ordered2 list of Access objects which represents the local order of execution. Operations preControl and postControl verify whether the local access order conforms with the global access order. Operation schedule determines which delayed accesses 1 It can be a partial order, two transactions are not necessarily ordered. 2 It can be a partial order, two accesses are not necessarily ordered.
7
may be resumed after a transaction commits or aborts. The Controller integrates these operations such that a consistent policy is implemented.
Access. Identifies a transaction access within the context of an object and contains its status. The access status can be: executing (exec), pre-pending (pre), post-pending (post), terminated (ter), committed (comm) or aborted (abor). A single Access object is created even if the object is recurrently accessed by the same transaction. Activity Context. The Activity Context encapsulates the concurrency generation policy, decoupling it from the serialization policy. Object. Shared data and behavior are encapsulated by object Object. Accesses to this object are serialized according to the global serialization policy.
4.3 Collaborations The Booch interaction diagrams of figures 4 and 5 illustrate collaborations of, respectively, global order definition and local order control in the Global Serialization pattern. coord:Transaction Manager
ACCESS OBJECT
CHANGE CONTEXT
tr:Transaction
REGISTER TRANSACTION
statOrder(tr) cmpOrder(tr1,tr2)
COMPARE ORDERS
dynOrder(tr1,tr2)
DYNAMIC DEFINE ORDER finishTr(tr) prepareTr(tr)
FOR ALL part ACCESSED BY tr ASK TO PREPARE
FINISH
ti:Transactional Interface
registerTr(tr)
IF FIRST TIME IN THIS CONTEXT STATIC DEFINE ORDER
CHOOSE A COORDINATOR
COMMIT
part:Transaction Manager
hybOrder(tr)
HYBRID DEFINE ORDER
prepare(tr)
FOR ALL ti ACCESSED BY tr ASK TO PREPARE BUILD GLOBAL ORDER
checkOrder()
IF ALL eCONTINUE AND ACYCLIC ORDER COMMIT
startCommit(tr)
IF ANY eERROR OR CYCLIC ORDER ABORT
startAbort(tr)
commitTr(tr)
FOR ALL part ACCESSED BY tr COMMIT
commit(tr)
FOR ALL ti ACCESSED BY tr COMMIT
Figure 4: Global Serialization: Global Order Definition. Collaborations for global order definition have four phases which are illustrated in figure 4. The fourth phase could be Commit Transaction or Abort Transaction. Since they are similar only the former is presented. 1. Transaction Context Change. A transaction can execute in the context of several Transaction Manager objects. When a transaction changes context (operation context) it registers itself in a Transaction Manager object (operation registerTr). The Transaction Manager may define the transaction’s global execution order (operation statOrder), in which case a static global serialization policy is being implemented. Transactions must not access objects without registering themselves in a Transaction Manager context. 2. Access Object. During an object access the order may be defined (operation dynOrder), in which case a dynamic global serialization policy is being implemented. This phase occurs before or after the Finish Transaction phase depending on the local serialization policy being implemented. (This phase is completely described in figure 5.)
8
3. Finish Transaction. When a transaction finishes it chooses a Transaction Manager object to start the consistency verification of its execution order. The selected Transaction Manager object is the coordinator and the others, which may include the coordinator, are the participants. The coordinator asks the participants to prepare for committing the transaction (operation prepareTr) and all participants that implement a hybrid global serialization policy (operation hybOrder) define the transaction’s execution order. Afterwards, the objects accessed by the transaction in the context of the participant are required to check their compatibility with the global order (operation prepare). If all object’s local orders conform with each participant’s global order, the coordinator is informed and receives the participants global execution order. The coordinator uses the participant’s orders to verify if they are consistent (operation checkOrder). Finally the coordinator starts a commit or an abort procedure. 4. Commit Transaction. The coordinator Transaction Manager requests participants to commit (operation commitTr). All objects accessed by the transaction in the context of each participant’s transaction manager are requested to commit the transaction’s modifications. For the sake of simplicity, the interaction diagram does not show changes done by the Transaction Manager object to the ordered list of transactions and to their status (e.g. comm after committing the transaction).
PRE CONTROL
CREATE ACCESS
ti:Transactional Interface
PREPARE
a:Access
tr:Transaction
:Transaction Manager
:Activity Context
tr=getCurrent()
WHICH TRANSACTION? REGISTER AT TRANSACTION
register(ti)
CREATE ACCESS
a=cons(ACCESS)
FOR ALL acs IN select1 VERIFY COMPABILITY OF ACCESS ORDER
preControl(a)
cmpOrder(a.tr,acs.tr) insert(a)
IF NOT eERROR
update(exec)
IF eCONTINUE delay(a.tr)
IF eDELAY
handle(a)
IF eERROR
GET ACCESS OF TRANSACTION
a=getAccess(tr)
FOR ALL acs IN select2 VERIFY COMPABILITY OF ACCESS ORDER
postControl(a)
cmpOrder(a.tr,acs.tr) update(ter)
IF NOT eDELAY
update(post)
IF eDELAY delay(a.tr)
IF eDELAY IF NOT eDELAY RETURN RESULT
return()
a=getAccess(tr)
GET ACCESS OF TRANSACTION
COMMIT
:Controller
do(a)
COMMIT EFFECTS RE-SCHEDULE PENDING ACCESSES AND UPDATE ACCESS STATUS
schedule(a)
FOR ALL acs IN select3 AWAKE DELAYED TRANSACTIONS
awake(acs.tr)
update(comm)
Figure 5: Global Serialization: Local Order Control. The collaboration phases of local order control are described in figure 5. The last phase could be Commit Access or Abort Access. Since they are similar only the former is described. 1. Access Creation. This phase creates the Access object and registers the Transactional Interface object in the Transaction object. Operation getCurrent returns the accessing transaction. If the transaction has already accessed the object an Access object is not created. 2. Pre-Control Access. When a pessimistic policy is being implemented this phase verifies whether the transactional access can proceed, be delayed or be aborted. The Controller object verifies if the access order is compatible with the order of other object accesses (operation preControl). Selection select1 9
represents the set of Access objects for which compatibility will be verified. This set and the behavior of the Controller’s preControl operation are policy dependent and should be redefined in subclasses. If the transaction has already accessed the object this phase does not occur, and the invocation proceeds immediately. 3. Prepare Access. When an optimistic policy is being implemented this phase verifies whether a transactional access to an object is serializable according to the Transaction Manager global serialization order. The serialization verification (operation postControl) checks if the local order of execution conforms with the global order of execution. It changes the status of Access objects to reflect the current access status. During this phase it may be necessary to suspend the transaction to wait for the termination of another transaction. Selection select2 represents the set of Access objects involved in compatibility verification. This set and the behavior of the Controller’s postControl operation are policy dependent. 4. Commit Access. This phase commits the changes done by the committing transaction to the object and re-schedules delayed accesses. Awake accesses must re-execute preControl and postControl operations if their status are, respectively, pre or post. Selection select3 and the particular behavior of the Controller’s schedule operation are policy dependent.
4.4 Policies The Global Serialization pattern supports several policies for global and local serialization. The former defines the global order of execution while the latter conforms, on a per-object basis, with that order. The pattern allows the heterogeneous combination of policies. 4.4.1
Global Serialization
Global serialization policies define an execution order using one of three different generic policies: static, dynamic and hybrid. These policies can appear heterogeneously integrated in an application. The different policies are coded in subclasses of Transaction Manager.
Homogeneous Policies. Homogeneous policies involve a single Transaction Manager object which defines the serialization order. – Static policies define the transaction’s execution order when the transaction registers itself for the first time in the Transaction Manager object. Operation statOrder of Transaction Manager orders the transaction after all other transactions. When using this policy, operation cmpOrder never returns unknown. Operations dynOrder and hybOrder are not defined by static policies. – Dynamic policies define the transaction’s execution order during transaction execution. The order is defined as objects are being accessed: operation dynOrder of Transaction Manager orders its first argument after the second if the transactions have not been ordered yet. Operations statOrder and hybOrder are not defined by dynamic policies. – Hybrid policies define the global order of execution when the transaction finishes. Operation hybOrder of Transaction Manager object orders the transaction after the preparing and committed transactions. Operations statOrder and dynOrder are not defined by hybrid policies.
Heterogeneous Policies Heterogeneous policies involve several Transaction Manager objects, supporting different global serialization policies, which should agree on the final serialization order. Heterogeneous Transaction Manager objects can be autonomous and cooperate. – Autonomous policies. A policy is autonomous if its integration with other policies does not imply their modification. Autonomous Transaction Manager objects communicate using only the abstract interface. Autonomous policies improve application extensibility.
10
– Cooperating policies. Transaction Manager objects supporting a cooperating policy define particular communication protocols to improve the degree of concurrency of the application. For instance, if a Transaction Manager object knows how two transactions are ordered by another Transaction Manager object it can immediately decide to abort a transaction without waiting for the prepare phase. These policies do not allow applications to be easily extended. 4.4.2
Local Serialization
In this pattern local serialization policies define the local execution order based on the global serialization order. Two different policies are possible: pessimistic and optimistic. They should be integrated with the global serialization policies. This integration raises the incompatibility problem previously referred to [Weihl 89, Guerraoui 94]. The pattern supports both kinds of policies: dependent and orthogonal. The former expects a particular global serialization policy while the latter are independent from the global serialization policy being used. These policies are coded in subclasses of Controller.
Dependent Policies Dependent policies expect a particular global serialization policy. In the absence of this policy they are not able to serialize accesses. – Pessimistic policies control accesses during the pre-control phase verifying compatibility with other accesses. Pessimistic policies do not need to verify, during the prepare phase, if the local order of execution conforms with the global order of execution (selection select2 denotes an empty set of Access objects). Two well-known pessimistic policies are timestamping [Thomas 79] and two-phase locking [Eswaran 76]. Timestamping requires a Transaction Manager object supporting a static policy. The local serialization policy uses the global order to serialize accesses. Operation preControl of Controller constrains accesses according to the global order. Operation preControl aborts transactions with a lower order than committed transactions and delays transactions with an order higher than executing transactions. Two-phase locking requires a Transaction Manager object supporting a dynamic policy. When accessing an object the transaction is ordered after the committed transactions by operation dynOrder. Operation preControl of Controller delays the access execution if there are conflicting executing transactions. – Optimistic policies do not serialize accesses during the access phase but during the prepare phase. In these policies, since there is no previous order, it is meaningless to verify the execution order in the preControl operation because operation cmpOrder would return unknown (selection select1 denotes an empty set of Access objects). A well-known optimistic policy is described in [Kung 81]. This policy requires a Transaction Manager supporting a hybrid policy. The global order (defined when the transaction finishes) is used as the timestamp the local orders (list of Access objects) should conform with. Operation postControl of Controller verifies whether the transaction’s local order of execution does not conflict with the global order of execution.
Orthogonal Policies It is possible to define local serialization policies which are independent of the global serialization order and of the other local serialization policies. These policies improve application extensibility since it is possible to change the Transaction Manager’s policy without changing the local control policies. Guerraoui [Guerraoui 94] considers two kinds of orthogonal policies: pessimistic and optimistic. Note that both policies, pessimistic and optimistic, execute the pre-control phase. – Pessimistic policies, during pre-control phase, abort accessing transactions which do not respect the global serialization order, and delay transactions which conflict with accessing transactions or which are not ordered (operation preControl). – Optimistic policies, during pre-control phase, abort accessing transactions which do not respect the global serialization order, and locally order the transaction (list of Access objects) after all accessing transactions (operation preControl). Afterwards, during the prepare phase, it verifies if transactions in the list have already committed, otherwise the prepare phase is delayed.
11
5 Recovery Pattern The Recovery pattern defines a generic recovery algorithm. Object recovery uses information about object state and operation semantics. It decouples the recovery part from the object’s functional part. Different policies can be supported: undo, redo, copy and compensating.
5.1 Intent The Recovery pattern decouples object recovery policies from object-specific algorithm semantics.
5.2 Structure and Participants The Booch class diagram in figure 6 illustrates the Recovery pattern’s structure:
Recover
1
prepare() commit() rollback()
rec Recover Interface
log 1
current N
1
m()
Recover Point prepare() redo() undo()
obj Object rp=cons(Recover Point); obj=rec->prepare(rp); obj->m’(); if eERROR rec->rollback(rp); else rec->commit(rp);
1
m’() copy() applyRedo() applyUndo()
Figure 6: Recovery: Structure. The main participants in the Recovery pattern are:
Recover Interface. It is responsible for bounding object accesses with invocations to the Recover object. It accesses the Object returned by the prepare operation and constructs the Recover Point object with specific information about the operation and object being invoked. Effects of accesses made using this interface can be rollbacked or committed. Recover. Defines the generic part of the recovery policy. It refers to the current Object, which is used as the initial point for redoing and undoing accesses effects. Contains and manages a list (log) of Recover Points which are used to update the current Object. Recover integrates operations prepare, commit and rollback such that a consistent policy is implemented. Particular policies are implemented by reusable Recover subclasses. Recover Point. Defines object-specific semantics which are necessary for the generic algorithm. It is responsible for preparing, redoing, and undoing Object accesses. Operations prepare, redo, and undo know the object’s internal structure and which are the effects of operations accesses to the Object. Object. Encapsulates data that is accessed and which can be recovered. Defines operations to be used by Recover Point objects (copy, applyRedo, applyUndo).
5.3 Collaborations The Booch interaction diagram in figure 7 illustrates the collaborations between objects in the Recovery pattern: The diagram describes four phases of collaboration: 12
:Recover Interface CREATE RECOVER POINT
rp:Recover Point
:Recover
:Object
rp=cons(RECOVER POINT)
PREPARE
obj=prepare(rp) REGISTER RECOVER POINT
insert(rp) prepare(current)
PREPARE TO RECOVER RETURN OBJECT TO BE ACCESSED
copy()
returnObject()
COMMIT
commit(rp) current= redo(current)
REDO EFFECTS
applyRedo()
remove(rp)
REMOVE FROM LOG
destroy()
DELETE RECOVER POINT
ROLLBACK
rollback(rp) current= undo(current)
UNDO EFFECTS
applyUndo()
remove(rp)
REMOVE FROM LOG
destroy()
DELETE RECOVER POINT
Figure 7: Recovery: Collaborations. 1. Create Recover Point. This phase creates the Recover Point object, which identifies the access during the other phases. 2. Prepare. This phase adds a new Recover Point object to the log. The Recover Interface object sends a Recover Point object the Recover and receives as reply the Object that should be accessed. During this phase the Recover Point is inserted in the queue and data needed for recovery actions may be copied. The Recover object may return, to the Recover Interface, either the current Object or a copy of it, depending on the policy being implemented. 3. Commit. The commit phase performs the changes specified by the operation identified by the Recover Point object. In some policies the Object is already up-to-date, because the operation invocation was done on the current Object, while in others the current Object needs to be updated, because the operation is executed in a copy. This Recover Point objects is removed from the log. 4. Rollback. The rollback phase undoes changes done by the operation identified by the Recover Point object. In some policies the current Object is already consistent, because the access was done in a copy, while in others the current Object needs to be restored. This Recover Point object is removed from the log.
5.4 Policies The Recovery pattern supports several policies and decouples the generic part of the policies from the objectspecific part. 5.4.1
Generic Part
These policies are supported by Recover objects. Recovery policies can use two different generic algorithms: Undo, when the current Object can be inconsistent, and Redo, when the the current Object is always consistent.
Undo policies reflect changes directly in the current Object object. To implement an Undo policy the Recover object should return the current Object at the end of the prepare phase. Afterwards, if a rollback happens, it is necessary to undo the changes made to the current Object, otherwise (commit happens) nothing needs to be done. 13
Redo policies do not change the current Object. To implement a redo policy the Recover object should return a copy of the current Object at the end of the prepare phase. Afterwards, if a commit happens it is necessary to propagate the changes to the current Object, otherwise (abort happens) nothing needs to be done. 5.4.2
Object-Specific Part
The generic policies for recovery can use two kinds of Recover Point objects: copy and compensating. The former, copying state, uses copies of the object’s state. The latter, compensating operations, uses operations that know how to return to a previous state based on the current state. These policies are supported by Recover Point objects.
Copy policies use copies of the object’s state to redo and undo effects. Operation prepare of the Recover Point object creates a copy of the current Object and returns it to the Recover object. Operations redo and undo use the copy to change the current Object. Undo policies do not invoke operation redo of the Recover Point object while Redo policies do not invoke its undo operation. Compensating policies use the current state instead of doing recovery based on data copies. In this policy it is not necessary to copy the object data during the prepare phase. The Recover Point object holds operations that know how to redo and undo accesses effects without using a previous object’s state. The compensating operations can be applied for both kinds of policies: Undo and Redo. The former compensates accesses effects while the latter re-does accesses effects.
6 Atomic Object This section discusses an integration of patterns which support an atomic object. The integration emphasizes the trade-offs between the policies offered by each of the participating patterns.
6.1 Intent This pattern supports several solutions for controlling accesses to a shared object; accesses are serializable and recoverable. Serialization is based on both local object semantics and global order.
6.2 Structure and Participants The Booch class diagram in figure 8 illustrates part of the Atomic Object structure: From the integration perspective the relevant participants are:
Atomic Interface. It is responsible for serializing and, if needed, for recovering accesses. Atomic Interface objects integrate the interfaces and behavior of Serial Interface, Transactional Interface and Recover Interface objects. It aggregates a Controller object and a Recover object. Controller. It is responsible for the serialization policies. Integrates the interfaces and behavior of Controller objects of the Local and Global Serialization patterns. Access. Integrates the interfaces and behavior of Access objects of the Local and Global Serialization patterns. Moreover, it holds a Recover Point object which is responsible for access recovery. Whenever a transaction accesses an object new Access Mode and Recover Point objects should be created. For instance, the first access is a read and the second is a write or compensating operations are being used.
14
tr Access
am
N Controller Activity Context
1
1
rp
preControl() postControl() schedule()
Transaction
1
1
req1 Access Mode
1
0-1 1 Atomic Interface
ctl
1
Require
1
Object m’()
obj
m() prepare() commit() abort()
rec
N
1
Recover Point
current 1
Recover
1
prepare() commit() rollback()
log
Figure 8: Atomic Object: Structure (fragment).
6.3 Collaborations The Booch interaction diagram in figure 9 illustrates the collaborations between objects within the Atomic Object during the pre-control phase: :Atomic Interface
:Controller
preControl(a) VERIFY GUARD FOR ALL acs IN select1 VERIFY ACCESS MODE
a.am->compatible(acs.am)
insert(a)
PRE CONTROL
update(exec)
IF eCONTINUE
IF eERROR
ADD RECOVER POINT PREPARE TO RECOVER
:Recover Point
a.req->guard()
IF NOT eERROR
IF eCONTINUE CREATE RECOVER POINT
:Recover
cmpOrder(a.tr,acs.tr)
VERIFY ACCESS ORDER
IF eDELAY
a:Access
delay(a) handle(a) rp=cons(RECOVER POINT) addRecover(rp) obj=prepare(rp)
Figure 9: Atomic Object: Collaborations (fragment). Pre-Control Phase. This phase serializes the new access and prepares it for recovery. It integrates the pre-control phase of the Local Serialization pattern with the pre-control phase of the Global Serialization pattern and the create recover point and prepare phases of the Recovery pattern. The phases of Global and Local Serialization patterns are highly integrated. Order compatibility is verified after access mode compatibility, and only if the former returns eCONTINUE, such that, if the policy is pessimistic and dynamic the accessing transaction is ordered only after committed transactions. The phases of the Recovery pattern occur after serialization and only if it returns eCONTINUE. Note that the Recover Point object is added to the Access.
6.4 Policies Each patterns allow several policies. However, not all combinations are possible. 15
6.4.1
Serialization and Recovery
During serialization it may be necessary to recover the object’s state due to synchronization conflicts. The integration of recovery with serialization is not straightforward: some combinations are not possible and other penalize performance. [Weihl 93] proves that some compatibility relations between access operations require a particular kind of recovery policy, e.g. forward commutativity requires an Undo recovery policy. Common combinations are optimistic serialization policies with recovery redo policies (optimize abort) and pessimistic serialization with undo policies (optimize commit). The designer must be aware of the allowed and best combinations. 6.4.2
Global Serialization and Local Serialization
The integration of the local part of both patterns, global serialization and local serialization, is easily defined, above, by a single Controller object which coordinates the local control with the global order. However, the integration, in the same application, of objects using global serialization and local serialization policies should be done with care and corresponds to the coexistence of transactional objects, atomic objects and non-atomic objects. The integration of transactions, atomic objects and non-atomic objects can be done according to different policies. For instance, consider the following two cases:
[Kaiser 93] considers atomic blocks and transactions which are serialized together. An atomic block defines an atomic access to an object. Each invocation to an atomic object generates a global (fake) transaction which commits as the access finishes. In this case object atomicity, as defined at the object level, is always respected. [Chen 89] considers different types of consistency which are defined on a per-operation and pertransaction basis. When a transaction accesses an object it verifies if the operation consistency is compatible with its consistency. If the consistent types are different the transaction dynamically transforms its consistency type to match the operation’s consistency type.
7 Implementation Remarks This section explains some implementation details which clarify the patterns’ abstractions.
Global Serialization Policies and Distribution. The Global Serialization pattern considers a global serialization policy per Transaction Manager object. A typical implementation of distributed applications uses a homogeneous Global Serialization policy per distribution unit and heterogeneous policies among them. In this case, each distribution unit has a Transaction Manager object associated with it which is enriched with a two-phase commit protocol [Gray 79]. However, it is possible to have a single Global Serialization policy between distribution units by creating several Transaction Manager objects using the same policy. They should share the order’s information accessed and updated by operations statOrder, dynOrder, hybOrder and cmpOrder. In a distributed implementation, when a transaction accesses a distribution unit it should register itself in the new context (operation context). Concurrency Generation Policies Several concurrency generation policies may be used to implement the serialization patterns. At the architecture level several implementations are possible: a single per transaction activity which animates the transaction, the transaction manager and the object the transaction accesses; or a set of private activities, one per object. Concurrency generation policies are encapsulated by Activity Context objects. Let’s consider two cases: a private object activity per accessed object and a private transaction activity which acts upon the objects:
16
– Private object activity. The implementation of the Activity Context object using a private object activity requires special care. In particular, operation delay cannot block the executing activity because a deadlock would occur. Object activity contexts require a scheduler to manage a queue of pending requests. These are objects which contain the access operation and are invoked by the scheduler. An example of this architecture is described by the Active Object pattern in [Lavender 95]. Implementation of operations delay and awake raises exceptions to be caught by the scheduler, thus allowing transaction switching. The scheduler handles exceptions by moving requests among different pending queues (operations delay and awake). – Private transaction activity. It is simpler to implement the Activity Context object in a private transaction activity context: operation delay blocks the activity and operation awake resumes the activity.
Recovery and failure. The implementation of the Recovery pattern may have to consider object robustness. Two problems inherent to crash recovery are: disk atomicity is different from object atomicity (writing an object on disk may not correspond to writing a single atomic block), and disk accesses should be delayed until enough data is available to be written to disk (main memory is much faster than disk and the application should not be delayed). To solve these problems it is necessary to write the set of recover points in a single block which will be atomically written to disk. Furthermore, is necessary to define a special kind of recover point to mark success or failure of an access. These marks are used to know if an update to disk should be recovered or not (a crash can happen when writing recover points on disk). The use of a write-ahead technique is required when writing objects to disk (a crash can happen when writing objects on disk).
8 Conclusions Development of concurrent applications is a non trivial task. We propose patterns to assist in the development process of concurrent applications. The introduction of software components with predefined architectures into the development process improves understanding of complex systems, reuse of generic solutions, application evolution, and process management [Garlan 95]. It is not the goal of patterns to offer a set of black boxes that could be used by everyone, but to clarify what are the problems of developing concurrent applications and how they can be handled. Our approach is to be used by people aware of development difficulties. Default and closed solutions can be constructed from our patterns, but we believe that they will only be blindly used in a few cases. This paper describes atomicity in terms of object-oriented design patterns. Each pattern is a minimal architectural element supporting different policies. The combination of patterns constitutes a framework which allows the construction of heterogeneous applications (in terms of used policies). The main benefit of our results is the definition of solutions for each of the identified policies and their integration into constructive larger solutions. Other patterns for concurrency [Lavender 95, Schmidt 95] do not emphasize policy separations and their constructive integration. When using the described patterns it is possible to start from a sequential application and enrich it with concurrency control and concurrency generation. First, concurrency control is incorporated, using the described patterns, and afterwards concurrency generation. The incorporation of concurrency control can use, for tuning, extensibility and adaptability, the power offered by decoupling the policies already described. Test and debug of concurrency control can be done in a amicable concurrent environment due to the decoupling between concurrency control and concurrency generation policies. For instance, it is difficult to debug an active object and so initially all objects should be implemented as passive. Afterwards, when concurrency control is debugged, the final concurrent generation policies should be implemented. Two problems should be solved to allow for extensible construction of concurrent applications: decoupling and composition of solutions. This paper deals with the former. The latter is dealt with by using composition techniques, e.g. in [Stroud 95] reflection is used to incorporate concurrency control policies within the application and allowing dynamic configuration of policies. Since the patterns decouple object semantics from serialization semantics they do not inhibit reuse. This problem is usually described as the inheritance anomaly [Matsuoka 93]. Nevertheless, the problem is only 17
partially solved since it is also a composition problem, and a complete solution must consider composition techniques. In the near future we intend to explore this set of patterns in order to support non-serial policies for cooperative models [Barghouti 91]. These patterns are part of a framework supporting heterogeneous concurrency control policies to be used in distributed applications [Silva 96] and it is being implemented on top of the ACE environment [Schmidt 94].
Acknowledgments We would like to thank David Matos for his careful proofreading.
References [Baquero 95]
Carlos Baquero, Rui Oliveira, and Francisco Moura. Integration of concurrency control in a language with subtyping and subclassing. In Conference on Object Oriented Technologies and Systems. Usenix Association, June 1995.
[Barghouti 91]
Naser S. Barghouti and Gail E. Kaiser. Concurrency Control in Advanced Database Applications. ACM Computing Surveys, 23(3):269–317, September 1991.
[Booch 94]
Grady Booch. Object-Oriented Analyis and Design with Applications. The Benjamin/Cummings Publishing Company, Inc., 1994.
[Caromel 93]
Denis Caromel. Towards a Method of Object-Oriented Concurrent Programming. Communications of the ACM, 36(9):90–102, September 1993.
[Chen 89]
Raymond C. Chen and Partha Dasgupta. Linking Consistency with Object/Thread Semantics: An Approach to Robust Computation. In In 9th International Conference on Distributed Computing Systems, pages 121–128, Newport Beach CA, June 1989.
[Ciancarini 95]
Paolo Ciancarini, Oscar Nierstrasz, and Akinori Yonezawa, editors. Object-Based Models and Languages for Concurrent Systems. Springer-Verlag, 1995.
[Eswaran 76]
K.P. Eswaran, J.N. Gray, R.A. Lorie, and I.L. Traiger. The Notions of Consistency and Predicate Locks in a Database System. Comunications of the ACM, 19(11):624–633, November 1976.
[Fazzolare 93]
Michael Fazzolare, Bernhard G. Humm, and R. David Ranson. Concurrency control for distributed nested transactions in hermes. International Conference for Concurrent and Distributed Systems, 1993.
[Gamma 95]
Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley, 1995.
[Garlan 95]
David Garlan. Research Directions in Software Architecture. ACM Computing Surveys, 27(2):257–261, June 1995.
[Georgakopolous 91] Dimitrios Georgakopolous, Marek Rusinkiewicz,and Amit Sheth. On Serializability of Multidatabase Transactions Through Forced Local Conflits. In IEEE Proceedings of the Seventh International Conference on Data Engineering, pages 314–323, Kobe, Japan, 1991. [Gray 79]
Jim Gray. Notes on Data Base Operating Systems. In R. Bayer, R. M. Graham, and G. Seegmuller, editors, Operating Systems: An Advanced Course, pages 393–481. Springer-Verlag, New-York, 1979.
[Guerraoui 94]
Rachid Guerraoui. Atomic Object Composition. In ECOOP’94, pages 118–138, Bologna, Italy, July 1994.
[Jacobson 92]
Ivar Jacobson. Object-Oriented Software Engineering. Addison-Wesley, 1992.
[Kaiser 93]
Gail E. Kaiser, Wenwey Hseush, Steven S. Popovich, and Shyhtsun F. Wu. Multiple Concurrency Control Policies in an Object-Oriented Programming System. In Gul Agha, Peter Wegner, and Akinori Yonezawa, editors, Research Directions in Concurrent Object-Oriented Programming, pages 195–210. MIT Press, 1993.
[Kung 81]
H. Kung and J. Robinson. On optimistic methods for concurrency control. ACM Transactions Database Systems, 6(2):213–226, June 1981.
[Lavender 95]
R. Greg Lavender and Douglas C. Schmidt. Active Object: an Object Behavioral Pattern for Concurrent Programming. In Pattern Languages of Programming Conference, Monticello, Illinois, September 1995.
18
[Liskov 88]
Barbara Liskov. Distributed Programming in Argus. Communications of the ACM, 31(3):300–312, March 1988.
[Lohr 93]
Klaus-Peter Lohr. Concurrency Annotations for Reusable Software. Communications of the ACM, 36(9):81–89, September 1993.
[Lopes 94]
Cristina Videira Lopes and Karl Lieberherr. Abstracting Process-to-Function Relations in ObjectOriented Applications. In ECOOP ’94, Bologna, Italy, July 1994.
[Matsuoka 93]
Satoshi Matsuoka and Akinori Yonezawa. Analysis of Inheritance Anomaly in Object-Oriented Concurrent Programming Languages. In Gul Agha, Peter Wegner, and Akinori Yonezawa, editors, Research Directions in Concurrent Object-Oriented Programming, pages 107–150. MIT Press, 1993.
[McCue 92]
Daniel L. McCue. Developing a Class Hierarchy for Object-Oriented Transaction Processing. In ECOOP’92, pages 414–426, Utrecht, The Netherlands, June/July 1992.
[Meyer 93]
Bertrand Meyer. Systematic Concurrent Object-Oriented Programming. Communications of the ACM, 36(9):56–80, September 1993.
[Parrington 88]
Graham D. Parrington and Santosh K. Shrivastava. Implementing Concurrency Control in Reliable Distributed Object-Oriented Systems. In ECOOP ’88, pages 233–249, Oslo, August 1988.
[Pu 88]
Calton Pu. Superdatabases for Composition of Heterogeneous Databases. In Proceedings of the 4th Data Engineering Conference, pages 548–555, Los Angeles, USA, February 1988. IEEE.
[Schmidt 94]
Douglas C. Schmidt. The ADAPTIVE Communication Environment: An Object-Oriented Network Programming Toolkit for Developing Communication Software. In 11th and 12th Sun User Group Conferences, San Jose, California and San Francisco, California, December 1993 and June 1994.
[Schmidt 95]
Douglas Schmidt and Charles Cranor. Half-Sync/Half-Async: An Architectural Pattern for Efficient and Well-Structured Concurrent I/O. In 2nd Pattern Language of Programming Conference, Monticello, Illinois, September 6-8 1995.
[Silva 95a]
´ Antonio Rito Silva, Joao Pereira, and Pedro Sousa. Local Serialization Pattern, October 1995. Presented at OOPSLA’95 Workshop on Design Patterns for Concurrent, Parallel, and Distributed ObjectOriented Systems.
[Silva 95b]
´ Antonio Rito Silva, Pedro Sousa, and Jos´e Alves Marques. Development of Distributed Applications with Separation of Concerns. In Proceedings of the 1995 Asia-Pacific Software Engineering Conference APSEC’95, pages 168–177, Brisbane, Australia, December 1995. IEEE Computer Society Press.
[Silva 96]
´ Antonio Rito Silva, Joao Pereira, and Jos´e Alves Marques. A Framework for Heterogeneous Concurrency Control Policies in Distributed Applications. In Proceedings of the 8th International Workshop on Software Specification and Design, pages 105–114, Schloss Velen, Germany, March 1996. IEEE Computer Society Press.
[Stroud 95]
R. J. Stroud and Z. Wu. Using Metaobject Protocols to Implement Atomic Data Types. In ECOOP ’95, pages 168–189, Aarhus, Denmark, August 1995.
[Thomas 79]
Robert H. Thomas. A Majority Concensus Approach to Concurrency Control for Multiple Copy Databases. ACM Transactions on Database Systems, 4(24):180–209, June 1979.
[Weihl 88]
William Weihl. Commutativity-Based Concurrency Control for Abstract Data Types. IEEE Transactions on Computers, 37(12):1488–1505, December 1988.
[Weihl 89]
William Weihl. Local Atomicity Properties: Modular Concurrency Control for Abstract Data Types. ACM Transactions on Programming Languages and Systems, 11(2):249–282, April 1989.
[Weihl 93]
William Weihl. The Impact of Recovery in Concurrency Control. Journal of Computer and System Sciences, 47(1):157–184, August 1993.
19