Guard Objects

For each different collection abstraction, a Guard class similar to IResourceLock has been defined and a corresponding typedef added:

template <class Element> class ICollectionGuard { ... };
typedef ICollectionGuard<Element> guard;

Essentially, a Guard object is an object created on a stack that is used to lock some other object. Guard objects are useful in C++ because they respond properly to exceptions. When an exception is thrown while still in the scope of the Guard object, its destructor is called as the exception passes through the stack frame and the destructor unlocks the target object. As a result, the exception can be caught and dealt with by code further up the call chain without leaving the locked object in an unusable locked state.

The Guard typedef can be used as if it was a nested class of a particular collection and is based on one of three new classes added to the IBM collection class wrapper:

template <class Element>
class ICollectionGuard {
public:
    ICollectionGuard(
        IACollection<Element>&, long timeout = -1);
    ~ICollectionGuard();

private:
    IACollection<Element>& ivCollection;
};

Instantiating a Guard Object

ICollectionGuard<Element> Constructor and Destructor

The Guard constructor takes the collection object to be locked and an optional timeout value as parameters. The timeout value is specified in milliseconds. If a lock request cannot be resolved within the specified range of time, an exception is thrown. The timeout value defaults to -1 to indicate an indefinite wait. The value 0 informs the constructor to throw an exception if the lock is not immediately available.

This parameter is only supported on non-POSIX platforms. Other platform implementations ignore the specification of this value.

The Guard destructor unlocks the Collection specified within the constructor of the Guard.

Guard Copy Constructor

The Guard copy constructor is made private in order to prevent the user from copying Guard objects.

Collection Constructor and Destructor

The collection does not keep track of all possible Guard objects currently in use with the target collection. Guards for a collection must be destructed before the collection itself is destructed. This is normally accomplished by declaring the Guard within a compound statement so that it is automatically destructed when the statement passes out of scope.

Collection Copy Constructor

If a new collection is created from an existing collection instance, the guards of the existing collection have no effect on the new collection.

Return Codes and Exceptions

Since the Guard is constructed, there are no return codes. The Collection classes use exceptions to indicate that a lock cannot be obtained. The user must code the Guard constructor within a try/catch clause. When the Guard constructor fails and the lock was not obtained for any reason, a C++ exception is thrown.

Deadlocks

In either of the above cases, you are responsible for the proper sequence of obtaining the locks. There is no special code within the collection classes to prevent the user from producing deadlocks.

Using Guard Objects

In a user program, a Guard is used in the following way to obtain a lock on a specific collection:

...

{
    ISet<char> my_set;
    try {
        ISet<char>::Guard g(my_set);
        my_set.add('x');
    }
    catch (IException& e) {
        // handle exception
    }
}

...

The critical region, in this case the add method invoked on the ISet<char>, must be specified within a C++ compound statement. On entry to the block, the Guard constructor locks the collection that is specified as the Guard constructor parameter. The destructor is executed when the scope of the block is left at the time the collection is unlocked. The specified name of the Guard object (g in the above example) is arbitrary and plays no role in the locking.

Depending on the number of threads of a particular user application, multiple Guard objects may exist that work with the same collection object.

For the Restricted Access Collections and the Tree Collections, two similar Guard classes and corresponding typedefs are added. They are exposed to the user through the following typedefs on the level of appropriate concrete collections:

typedef IRestrictedAccessCollectionGuard<Element> guard;
typedef ITreeGuard<Element> guard;

In the event that the user invokes a Collection method that involves two or even three collections, code such as the following must be used in order to achieve thread-safe execution:

...

{
    try {
        ISet<char>::Guard l1(my_set1);
        ISet<char>::Guard l2(my_set2);
        my_set1.addAllFrom(my_set2);
    }
    catch (IException& e) {
        // handle exception
    }
}

...

In the case of three involved collections, the following code must be used:

...

{
    try {
        ISet<char>::Guard l1(my_set1);
        ISet<char>::Guard l2(my_set2);
        ISet<char>::Guard l3(my_set3);
        my_set1.addInterSection(my_set2,my_set3);
    }
    catch (IException& e) {
        // handle exception
    }
}

...

In cases such as these, where multiple locks must be acquired, it is important that each section of code that acquires the locks do so in the same order. Not doing so can result in deadlocks.

The programmer does not need to include any new header files. The typedef for the ISet coding samples illustrated above is provided by the standard include file iset.h.



Introduction to the Collection Classes
Collection Class Hierarchy
Overall Implementation Structure
Thread Safety and the Collection Classes


Adding an Element to a Collection
Removing an Element from a Collection
Using Cursors to Locate and Access Elements
Using Cursors to Iterate Over a Collection
Using allElementsDo and Applicators to Iterate Over a Collection
Cursors vs. Exception Handling
Instantiating the Collection Classes