![]() |
![]() |
![]() |
![]() |
OrbixOTS Programmer's and Administrator's Guide |
OrbixOTS includes an implementation of the OMG Object Concurrency Control Service (OCCS). This can be used to control concurrent transactions as they access a shared set of resources. Though the OCCS is a separate service, it is tightly integrated with the transaction service. The following sections describe the OCCS and demonstrate how it is used.
Note that XA resource managers provide their own concurrency control and the OCCS is typically not required. The OCCS is useful when using the Resource interface to implement recoverable resources.
The OCCS uses locks to control concurrent transactions. Before a transaction can access a shared resource a lock must be acquired on behalf of the transaction. Several lock modes are supported to increase the level of concurrency. If a transaction tries to acquire a lock in a mode that conflicts with a lock held by another transaction, the request is either denied or blocked until the conflict is resolved.
Figure 7.1: Associating Lock Sets and Resources
A lock set is a collection of locks that is associated with a resource, as shown in Figure 7.1. This association is made by the application and reflects the granularity of resources. For example, a resource could be a single object or a collection of objects. The former permits more concurrency but requires more locks, while the latter has fewer locks but greatly reduces the degree of concurrency.
Similar to the way in which transaction contexts can be propagated from a client to a server implicitly or explicitly, the OCCS provides implicit and explicit lock sets. With implicit lock sets, all operations are performed on behalf of the current transaction. With explicit lock sets, the identifier of the transaction, in the form of a reference to a coordinator object, is passed as a parameter to the operations.
The OCCS also allows implicit lock sets to be used outside of a transaction. Here, the requests are made on behalf of the current thread of control.
The OCCS supports five different lock modes: read, write, upgrade, intention-read, and intention-write. Table 7.2 shows the conflict matrix for each mode (where l indicates a conflict). A conflict occurs when a transaction requests a lock and at least one unrelated transaction holds a lock in a conflicting mode. Requests to acquire a lock that result in a conflict will either fail or cause the request to block.
| Requested Mode | |||||
| Granted Mode | IR | R | U | IW | W |
| Intention Read (IR) | l | ||||
| Read (R) | l | l | |||
| Upgrade (U) | l | l | l | ||
| Intention Write (IW) | l | l | l | ||
| Write (W) | l | l | l | l | l |
Table 7.2: Lock Mode Conflict Matrix
Standard multiple-readers/single-writer transactions are supported with read and write locks. The upgrade lock is used to overcome a common deadlock scenario. Intention read and write locks are used to support locking hierarchies of resources. These lock modes are discussed in more detail in the sections that follow.
The OCCS supports conventional read/write locking which allows multiple readers but only a single writer. Transactions that want to read a resource must acquire a read lock, which will succeed only if there are no other transactions holding a write lock on the resource. Transactions that want to update a resource must acquire a write lock, which will succeed only if there are no other transactions holding either a read or a write lock on the resource.
Standard read/write locking can easily lead to deadlock when two or more transactions attempt to first read a resource and then later update the same resource. This is illustrated below where two transactions T1 and T2 acquire read and write locks on a resource x
Due to the order in which each transaction acquires the locks and the order in which the transactions are interleaved, a deadlock situation arises. Each transaction is attempting to acquire a write lock which is conflicting with the read lock held by the other transaction.
| Requested Mode | |||
| Granted Mode | R | U | W |
| Read (R) | l | ||
| Upgrade (U) | l | l | |
| Write (W) | l | l | l |
Table 7.3: Read/Write/Upgrade Conflict Matrix
To overcome this problem the OCCS supports upgrade locks. An upgrade lock is similar to a read lock except that it conflicts with itself. Table 7.3 shows the conflict matrix for read, write and upgrade locks. The resulting scenario is illustrated as follows:
Here, each transaction acquires an upgrade lock in anticipation that it will eventually want to acquire a write lock. Since an upgrade lock conflicts with itself, the transaction T2 is blocked trying to acquire the upgrade lock and T1 proceeds to acquire a write lock. When T1 releases its locks, T2 is granted the upgrade lock and can then acquire the write lock. Note that an upgrade lock does not prevent other transactions from acquiring read locks and reading the resource.
Many resources are hierarchical in nature. Consider the directory/file hierarchy in file systems and the database/table/row hierarchy in relational databases. The hierarchical nature of these resources may be exploited to reduce the number of locks that must be acquired for certain operations. To simplify the discussion consider the two-level hierarchy shown in Figure 7.4 on page 108, where there is a parent node P with 100 child nodes C1...C100.
Consider the following four transactions that want to perform certain operations:
T4: Read all children (C1...C100)
Using conventional locking, the first three transactions would acquire a read or write lock on the child node being accessed. Transaction T4 would have to acquire a read lock on the parent node and a read lock on each of the child nodes. In this example, T4 would acquire 101 locks but in a real database there might be thousands of records that need to be locked.
Figure 7.4: Hierarchical resources
A better solution is to have multiple granularity locks so that a read lock could be acquired on all child nodes. Here, T4 could just acquire a read lock on the parent node P. However, this still allows T1 and T2 write access to the child nodes C1 and C2, so these transactions would have to acquire a write lock on P. This naïve solution severely restricts concurrency, since locks are effectively held at the highest level. In a database this would mean acquiring read and write locks on the database itself!
The correct solution is to use intention locks, which provide variable granularity locks suitable for hierarchical resources. There are two types of intention locks: intention-read and intention-write locks.
Table 7.5 shows the conflict matrix for read, write and intention locks without upgrade locks.
| Requested Mode | ||||
| Granted Mode | IR | R | IW | W |
| Intention Read (IR) | l | |||
| Read (R) | l | l | ||
| Intention Write (IW) | l | l | ||
| Write (W) | l | l | l | l |
Table 7.5: Intention Lock Conflict Matrix
When using intention locks to access a hierarchy, the order in which locks are acquired is always from the top down, as shown in Figure 7.6. Transaction T1 first acquires an intention-write lock in the parent node P and then acquires a write lock on the child node C1. Similarly, T2 acquires an intention-write lock on P and a write lock on C2. Both transactions are granted access since they are working on different child nodes and intention-write locks do not conflict. Transaction T3 acquires an intention-read lock on P and a read lock on C3. Again there is no conflict, since all three transaction are accessing different child nodes and intention-read locks do not conflict with intention-write locks. Finally, T4 attempts to acquire a read lock on P, which is equivalent to acquiring read locks on all child nodes. This causes a conflict because a read lock conflicts with intention-write locks. When transactions T1 and T2 complete and drop their locks, T4 will be granted the read lock.
Figure 7.6: Hierarchical Locking using Intention Locks
When several transaction are run concurrently, the effect must be the same as running the transactions in some serial order. This is known as the serializability property. When using the OCCS (or any other concurrency control mechanism that uses locks) there is a simple technique that must be followed to ensure serializability, known as two-phase-locking (2PL).
Figure 7.7: Standard Locking
Figure 7.7 shows how 2PL works. There are two phases: the growing phase and the shrinking phase. All locks are acquired during the growing phase and no locks may be released. As soon as one lock is released the shrinking phase starts. In this second phase, locks can only be released and no new locks can be acquired.
A simpler variation on standard-2PL is strict-2PL which is shown in Figure 7.8. Here all locks are released when the transaction commits (or rolls-back) and no locks are released during the transaction. This is supported in the OCCS with a lock coordinator object that can release all locks held by a transaction. Strict-2PL decreases the level of concurrency between transactions, because locks are held for longer times.
Figure 7.8: Strict Two Phase Locking
Note that using standard-2PL can weaken the isolation property. Consider a transaction that acquires a write lock on a resource, modifies the resource and then releases the write lock. Another transaction can then read the modified resource and view the intermediate results of an incomplete transaction. For this reason, strict-2PL should be used in preference to standard-2PL unless your application can tolerate the weaker isolation levels.
The OCCS locking model provides multiple possession semantics. This means that a transaction may hold multiple locks in a lock set at any one time. In addition, a transaction may hold several locks in the same mode. Effectively a count is maintained per lock mode for each transaction holding locks in a lock set.
Table 7.9: Multiple Possession Semantics
To illustrate multiple possession semantics Table 7.9 shows the internals of a lock set as two transactions acquire and release locks over a short period.
Note: * x2 means the operation is repeat.ed
T1 starts by acquiring an intention read lock and a write lock. This is permitted because conflicts only occur between unrelated transactions.
T1 acquires the write lock, its intention read lock is not released.
T2 acquires two read locks and transaction T1 acquires another intention read lock. This increases the count of locks held for these transactions.
T1 unlocks a single intention read lock, the lock set still contains one intention read lock for T1 because its count is decreased to 1. When T1 unlocks its final read lock, the lock is released and its count is decreased to 0.
T2's attempt to acquire a write lock is denied since this conflicts with the read and intention read lock held by T1.
T2 drops its locks, all locks held by T1 are released.
The OCCS, like the transaction service, is implemented as a library and not as an external server program. The IDL for the OCCS is contained in the CosConcurrencyControl module and a C++ implementation for the IDL interfaces is available in all OrbixOTS servers. Within IDL files, the CosConcurrencyControl module may be accessed by including the file OrbixOTS.idl. Within server source files the C++ mapping is accessible by including the file OrbixOTS.hh.
The enumeration type lock_mode defines the five lock modes, as shown in Figure 7.10. There is one exception named LockNotHeld, which is used when a request to release a lock is made by a transaction that does not hold the lock.
Figure 7.10: Lock Modes and Exceptions
The interface LockSet in Figure 7.11 is used for implicit lock sets. Operations are provided to acquire and release locks on a lock set object on behalf of the current transaction. There is also an operation to get a reference to the transaction's lock coordinator so that all locks held by the transaction may be dropped when the transaction completes. Implicit lock sets are created using a lock set factory; see "Creating Lock Set Objects" on page 119.
Figure 7.11: IDL for Implicit Lock Sets
The operation lock() acquires a single lock in a specific mode. If the lock mode conflicts with another lock held by another unrelated transaction, the operation blocks until the conflict is resolved or until the requesting transaction rolls back.
The following code illustrates acquiring a read lock for the current transaction:
CosConcurrencyControl::LockSet_ptr lockset = ... lockset->lock(CosConcurrencyControl::read);
A lock() operation that blocks causes the request to be added to a queue. When the conflict is resolved, requests on the queue are serviced in a first-in first-out (FIFO) order.
If blocking when there is a conflict is unacceptable, the operation try_lock() can be used. This attempts to acquire a single lock in a specific mode, but if there is a conflict, a value of FALSE is returned. A return value of TRUE means that the lock was successfully acquired. For example, the following code attempts to acquire an upgrade lock:
CosConcurrencyControl::LockSet_ptr lockset = ... lockset->try_lock(CosConcurrencyControl::upgrade);
The operation unlock() releases a single lock in a specific mode. Note that because a transaction may hold several locks in the same mode, calling unlock() does not always release the lock. If the transaction does not hold a lock in the specified mode, the exception LockNotHeld is raised. The following code releases a single write lock on behalf of the current transaction:
CosConcurrencyControl::LockSet_ptr lockset = ...
try {
lockset->unlock(CosConcurrencyControl::write);
}
catch (CosConcurrencyControl::LockNotHeld) {
...
}
Releasing all locks held by the current transaction is done by invoking an operation on the transaction's lock coordinator. The get_coordinator() operation is used to obtain a reference to the lock coordinator. See "Dropping Locks" on page 120 for details.
Lastly, the operation change_mode() is provided to change the mode of a single lock. Both the original mode and the new mode are specified, and if the transaction does not hold a lock in the original mode the exception LockNotHeld is raised. For example, to change an upgrade lock to a write lock the following code may be used:
CosConcurrencyControl::LockSet_ptr lockset = ...
try {
lockset->change_mode(CosConcurrencyControl::upgrade,
CosConcurrencyControl::write);
}
catch (CosConcurrencyControl::LockNotHeld) {
...
}
Explicit lock sets are supported by the TransactionalLockSet interface shown in Figure 7.12. This provides the same operations as the interface LockSet, except the operations lock(), try_lock(), unlock() and change_mode() all take an extra parameter that is a reference to the transaction coordinator on whose behalf the operations are performed. Explicit lock sets are created using a lock set factory; refer to "Creating Lock Set Objects" on page 119 for details.
Figure 7.12: IDL for Explicit Lock Sets
The following code shows how an intention read lock is acquired on an explicit lock set:
CosConcurrencyControl::TransactionalLockSet_ptr lockset = ... CosTransactions::Coordinator_ptr coord = ... lockset->lock(coord, CosConcurrencyControl::intention_read);
Implicit and explicit lock sets are created using a lock set factory provided by the LockSetFactory interface, shown in Figure 7.13. Two operations are supported: create() returns a reference to a new implicit lock set object, and create_transactional() returns a reference to a new explicit lock set object.
Figure 7.13: IDL for Lock Set Factory
Each OrbixOTS server has a lock set factory object which can be obtained using the get_lockset_factory() operation provided by the OrbixOTS::Server class. Creating a lock set object involves first obtaining the lock set factory reference and invoking either create() or create_transactional(). This is illustrated with the following code:
OrbixOTS::Server_var ots = ... CosConcurrencyControl::LockSetFactory_var Factory = ots->get_lockset_factory(); // Create an implicit lock set object. CosConcurrencyControl::LockSet_ptr lockset = factory->create(); // Create an explicit lock set object. CosConcurrencyControl::LockSet_ptr lockset2 = factory->create_transactional();
The LockCoordinator interface shown in Figure 7.14 provides a means of dropping all locks held by a transaction. This is useful when using strict-2PL, where it is necessary to drop all locks when a transaction completes. Refer to "Two-Phase Locking" on page 110 for further information on this topic. The lock coordinator object is obtained by invoking the operation get_coordinator() on a lock set object.
Figure 7.14: IDL for Lock Coordinator
The following code illustrates dropping all locks held by a transaction in an implicit lock set. Note that when calling the get_coordinator() operation, a reference to the transaction's coordinator must be passed as a parameter.
CosTransactions::Coordinator_ptr coord = ... CosConcurrencyControl::LockSet_ptr lockset = ... CosConcurrencyControl::LockCoordinator_var lockCoord; // Get the lock coordinator from the lock set object. lockCoord = lockset->get_coordinator(coord); // Drop all locks. lockCoord->drop_locks();
Note that with nested transactions, locks should only be released when the sub-transaction rolls back. When a sub-transaction commits, its locks are inherited by the parent transaction.
|
support@iona.com Copyright © 2000, IONA Technologies PLC. |