2 Understanding the Coherence C++ Object Model

The Coherence Extend C++ API contains a C++ object model. You should become familiar with this object model if you want to implement the Coherence API. This section contains the following information:

2.1 Using the Object Model

The following section contains general information for writing code which uses the object model.

2.1.1 Coherence Namespaces

This coherence namespace contains the following general purpose namespaces:

  • coherence::lang—the essential classes that make up the object model

  • coherence::util—utility code, including collections

  • coherence::net—network and cache

  • coherence::stl—C++ Standard Template Library integration

  • coherence::io—serialization

Although each class is defined within its own header file, you can use namespace-wide header files to facilitate the inclusion of related classes. We recommend including, at a minimum, coherence/lang.ns in code that uses this object model.

2.1.2 Understanding the Base Object

The coherence::lang::Object class is the root of the class hierarchy. This class provides the common interface for abstractly working with Coherence class instances. Object is an instantiable class that provides default implementations for the following functions.

  • equals

  • hashCode

  • clone (optional)

  • toStream (that is, writing an Object to an std::ostream)

See coherence::lang::Object in the C++ API for more information.

2.1.3 Automatically Managed Memory

In addition to its public interface, the Object class provides several features used internally. Of these features, the reference counter is perhaps the most important. It provides automatic memory management for the object. This automatic management eliminates many of the problems associated with object reference validity and object deletion responsibility. This management reduces the potential of programming errors which may lead to memory leaks or corruption. This results in a stable platform for building complex systems.

The reference count, and other object "life-cycle" information, operates in an efficient and thread-safe manner by using lock-free atomic compare-and-set operations. This allows objects to be safely shared between threads without the risk of corrupting the count or of the object being unexpectedly deleted due to the action of another thread.

2.1.3.1 Referencing Managed Objects

To track the number of references to a specific object, there must be a level of cooperation between pointer assignments and a memory manager (in this case the object). Essentially the memory manager must be informed each time a pointer is set to reference a managed object. Using regular C++ pointers, the task of informing the memory manager would be left up to the programmer as part of each pointer assignment. In addition to being quite burdensome, the effects of forgetting to inform the memory manager would lead to memory leaks or corruption. For this reason the task of informing the memory manager is removed from the application developer, and placed on the object model, though the use of smart pointers. Smart pointers offer a syntax similar to normal C++ pointers, but they do the bookkeeping automatically.

The Coherence C++ object model contains a variety of smart pointer types, the most prominent being:

  • View—A smart pointer that can call only const methods on the referenced object

  • Handle—A smart pointer that can call both const and non-const methods on the referenced object.

  • Holder—A special type of handle that enables you to reference an object as either const or non-const. The holder remembers how the object was initially assigned, and returns only a compatible form.

Other specialized smart pointers are described later in this section, but the View, Handle, and Holder smart pointers will be used most commonly.

Note:

In this documentation, the term handle (with a lowercase "h") refers to the various object model smart pointers. The term Handle (with an uppercase "H") refers to the specific Handle smart pointer.

2.1.3.2 Using handles

By convention each managed class will have these nested-types corresponding to these handles. For instance the managed coherence::lang::String class defines String::Handle, String::View, String::Holder.

2.1.3.2.1 Assignment of handles

Assignment of handles follows normal inheritance assignment rules. That is, a Handle may be assigned to a View, but a View may not be assigned to a Handle, just like a const pointer cannot be assigned to a non-const pointer.

2.1.3.2.2 Dereferencing handles

When dereferencing a handle that references NULL, the system will throw a coherence::lang::NullPointerException instead of triggering a traditional segmentation fault.

For example, this code would throw a NullPointerException if hs == NULL:

String::Handle hs = getStringFromElsewhere();
cout << "length is " << hs->length() << end1;

2.1.3.3 Managed Object Instantiation

All managed objects are heap allocated. The reference count—not the stack—determines when an object can be deleted. To prevent against accidental stack-based allocations, all constructors are marked protected, and public factory methods are used to instantiate objects.

The factory method is named create and there is one create method for each constructor. The create method returns a Handle rather than a raw pointer. For example, the following code will create a new instance of a string:

String::Handle hs = String::create("hello world");

By comparison, these examples are incorrect and will not compile:

String str("hello world);
String* ps = new String("hello world);

2.1.4 Managed Strings

All objects within the model, including strings, are managed and extend from Object. Instead of using char* or std::string, the object model uses its own managed coherence::lang::String class. The String class supports ASCII and the full Unicode BML character set.

2.1.4.1 String Instantiation

String objects can easily be constructed from char* or std::string strings, as shown in these examples:

Example 2-1 Examples of Constructing String Objects

const char*    pcstr = "hello world";
std:string     stdstr(pcstr);
String::Handle hs   = String::create(pcstr);
String::Handle hs2  = String::create(stdstr);

The managed string is a copy of the supplied string and contains no references or pointers to the original. You can convert back, from a managed String to any other string type, by using getCString() method. This returns a pointer to the original const char*. Strings can also be created using the standard C++ << operator, when coupled with the COH_TO_STRING macro.

Example 2-2 Constructing String Objects with the "<<" Operator

String::Handle hs = COH_TO_STRING("hello " << getName() << " it is currently " << getTime());

2.1.4.2 Auto-Boxed Strings

To facilitate the use of quoted string literals, the String::Handle and String::View support auto-boxing from const char*, and const std::string. This enables you to write the code shown in the prior samples as:

Example 2-3 Autoboxing Examples

String::Handle hs  = "hello world";
String::Handle hs2 = stdstr;

Auto-boxing is also available for other types. See coherence::lang::BoxHandle for details.

2.1.5 Type Safe Casting

Handles are type safe, in the following example, the compiler will not allow you to assign an Object::Handle to a String::Handle, because not all Objects are Strings.

Object::Handle ho = getObjectFromSomewhere();
String::Handel hs = ho; // will not compile

However, this example will compile, as all Strings are Objects.

Example 2-4 Type Safe Casting Examples

String::Handle hs = String::create("hello world");
Object::Handle ho = hs; // will compile

2.1.5.1 Down Casting

For situations in which you want to down-cast to a derived Object type, you must perform a dynamic cast using the C++ RTTI (runtime type information) check and ensure that the cast is valid. The Object model provides helper functions to ease the syntax.

  • cast<H>(o)—attempt to transform the supplied handle o to type H, throwing an ClassCastException on failure

  • instanceof<H>(o)—test if a cast of o to H is allowable, returning true for success, or false for failure

These functions are similar to the standard C++ dynamic_cast<T>, but do not require access to the raw pointer.

The following example shows how to down cast a Object::Handle to a String::Handle:

Example 2-5 Down Casting Examples

Object::Handle ho = getObjectFromSomewhere();
String::Handle hs = cast<String::Handle>(ho);

The cast<H> function will throw a coherence::lang::ClassCastException if the supplied object was not of the expected type. The instanceof<H> function can be used to test if an Object is of a particular type without risking an exception being thrown. Such checks or generally only needed for places where the actual type is in doubt.

Example 2-6 Object Type Checking with the instanceof<H> Function

Object::Handle ho = getObjectFromSomewhere();

if (instanceof<String::Handle>(ho))
  {
  String::Handle hs = cast<String::Handle>(ho);
  }
else if (instanceof<Integer32::Handle>(ho))
  {
  Integer32::Handle hn = cast<Integer32::Handle>(ho);
  }
else
  {
  ...
  }

2.1.6 Managed Arrays

Managed arrays are provided by using the coherence::lang::Array<T> template class. In addition to being managed and adding safe and automatic memory management, this class includes the overall length of the array, and bounds checked indexing.

You can index an array by using its Handle's subscript operator, as shown in this example:

Example 2-7 Indexing an Array

Array<int32_t>::Handle harr = Array<int32_t>::create(10);

int32_t nTotal = 0;
for (size32_t i = 0, c = harr->length; i < c; ++i)
    {
    nTotal += harr[i];
    }

The object model supports arrays of C++ primitives and managed Objects. Arrays of derived Object types are not supported, only arrays of Object, casting must be employed to retrieve the derived handle type. Arrays of Objects are technically Array<MemberHolder<Object> >, and typedef'd to ObjectArray for easier readability.

2.1.7 Collection Classes

The coherence::util* namespace includes several collection classes and interfaces that may be useful in your application. These include:

These classes also appear as part of the Coherence Extend API.

Similar to ObjectArray, Collections contain Object::Holders, allowing them to store any managed object instance type.

Example 2-8 Storing Managed Object Instances

Map::Handle  hMap = HashSet::create();
String::View vKey = "hello world";

hMap->put(vKey, Integer32::create(123));

Integer32::Handle hValue = cast<Integer32::Handle>(hMap->get(vKey));

2.1.8 Managed Exceptions

In the object model, exceptions are also managed objects. This enables you to hold onto caught exceptions as a local variable or data member without the risk of object slicing.

All Coherence exceptions are defined by using a throwable_spec and derive from the coherence::lang::Exception class, which derives from Object. Managed exceptions are not explicitly thrown by using the standard C++ throw statement, but rather by using a COH_THROW macro. This macro will set stack information, and then call the exception's raise method, which ultimately calls throw. The resulting thrown object may be caught an the corresponding exceptions View type, or an inherited View type. Additionally these managed exceptions may be caught as standard const std::exception classes. The following example shows a try/catch block with managed exceptions:

Example 2-9 A Try/Catch Block with Managed Exceptions

try
    {
    Object::Handle h = NULL;
    h->hashCode(); // trigger an exception
    }
catch (NullPointerException::View e)
    {
    cerr << "caught" << e <<endl;
    COH_THROW(e); // rethrow
    }

Note:

This exception could also have been caught as Exception::View or const std::exception&.

2.1.9 Object Immutability

In C++ the information of how an object was declared (such as const) is not available from a pointer or reference to an object. For instance a pointer of type const Foo*, only indicates that the user of that pointer cannot change the objects state. It does not indicate if the referenced object was actually declared const, and is guaranteed not to change. The object model adds a runtime immutability feature to allow the identification of objects which can no longer change state.

The Object class maintains two reference counters: one for Handles and one for Views. If an object is referenced only from Views, then it is by definition immutable, as Views cannot change the state, and Handles cannot be obtained from Views. The isImmutable() method (included in the Object class) can test for this condition. The method is virtual, allowing subclasses to alter the definition of immutable. For example, String contains no non-const methods, and therefore has an isImmutable() method that always returns true.

Note that once immutable, an object cannot revert to being mutable. You cannot cast away const-ness to turn a Handle into a View as this would violate the proved immutability.

Immutability is important with respect to caching. The Coherence NearCache and ContinuouQueryCache can take advantage of the immutability to determine if a direct reference of an object can be stored in the cache, or if a copy must be created. Additionally, knowing that an object cannot change allows safe multi-threaded interaction without synchronization.

2.1.10 Integrating Existing Classes into the Object Model

Frequently there will be the need to integrate existing classes into the object model. A typical example would be the need to store a data-object into a Coherence cache, which only supports storage of managed objects. As it would not be reasonable to require that pre-existing classes be modified to extend from coherence::lang::Object, the object model provides an adapter which will automatically convert a non-managed plain old C++ class instance into a managed class instance at runtime.

This is accomplished by using the coherence::lang::Managed<T> template class. This template class extends from Object and from the supplied template parameter type T, effectively producing a new class which is both an Object and a T. The new class can be initialized from a T, and converted back to a T. The result is an easy to use, yet very powerful bridge between managed and non-managed code.

See the API doc for coherence::lang::Managed for details and examples.

2.2 Writing New Managed Classes

The following section provides information necessary to write new managed classes, that is, classes which extend from Object. The creation of new managed classes is required when you are creating new EventListeners, EntryProcessors, or Filter types. They are not required when you are working with existing C++ data objects or making use of the Coherence C++ API. See the previous section for details on integration non-managed classes into the object model.

2.2.1 Specification-Based Managed Class Definition

Specification-based definitions, or "specs" enables you to quickly define managed classes in C++.

Specification-based definitions are helpful when you are writing your own implementation of managed objects.

There are various forms of specs used to create different class types:

  • class_spec—standard instantiatable class definitions

  • cloneable_spec—cloneable class definitions

  • abstract_spec—non-instantiatable class definitions, with zero or more pure virtual methods

  • interface_spec—for defining interfaces (pure virtual, multiply inheritable classes)

  • throwable_spec—managed classes capable of being thrown as exceptions

Specs automatically define these features on the class being spec'd:

  • Handles, Views, Holders

  • static create() methods which delegate to protected constructors

  • virtual clone() method delegating to the copy constructor

  • virtual sizeOf() method based on ::sizeof()

  • super typedef for referencing the class from which the defined class derives

  • inheritance from coherence::lang::Object, when no parent class is specified by using extends<>

To define a class using specs, the class publicly inherits from one of the above specs. Each of these specs are parametrized templates. The parameters are as follows:

  • The name of the class being defined.

  • The class to publicly inherit from, specified by using an extends<> statement, defaults to extends<Object>

    • This element is not supplied in interface_spec

    • Except in the case of extends<Object>, the parent class is not derived from virtually

  • A list of interfaces implemented by the class, specified by using an implements<> statement

    • All interfaces are derived from using public virtual inheritance

Note that the extends<> parameter is note used in defining interfaces.

Example 2-10 illustrates using interface_spec to define a Comparable interface:

Example 2-10 An Interface Defined by interface_spec

class Comparable
    : public interface_spec<Comparable>
    {
    public:
        virtual int32_t compareTo(Object::View v) const = 0;
    };

Example 2-11 illustrates using interface_spec to define a derived interface Number:

Example 2-11 A Derived Interface Defined by interface_spec

class Number
    : public interface_spec<Number,
        implements<Comparable> >
    {
    public:
        virtual int32_t getValue() const = 0;
    };

Next a cloneable_spec is used to produce an implementation. This is illustrated in in Example 2-12.

Note:

To support the auto-generated create methods, instantiatable classes must declare the coherence::lang::factory<> template as a friend. By convention this is the first statement within the class body.

Example 2-12 An Implementation Defined by cloneable_spec

class Integer
    : public cloneable_spec<Integer,
        extends<Object>,
        implements<Number> >
    {
    friend class factory<Integer>;

    protected:
        Integer(int32_t n)
            : super(), m_n(n)
            {
            }

        Integer(const Integer& that)
            : super(that), m_n(that.m_n)
            {
            }

    public:
        virtual int32_t getValue() const
            {
            return m_n;
            }

        virtual int32_t compareTo(Object::View v) const
            {
            return getValue() - cast<Integer::View>(v)->getValue();
            }

        virtual void toStream(std::ostream& out) const
            {
            out << getValue();
            }

    private:
        int32_t m_n;
    };

The class definition in Example 2-12 is the equivalent the non-spec based definitions in Example 2-13.

Example 2-13 Defining a Class Without the use of specs

class Integer
    : public virtual Object, public virtual Number
    {
    public:
        typedef TypedHandle<const Integer> View;   // was auto-generated
        typedef TypedHandle<Integer>       Handle; // was auto-generated
        typedef TypedHolder<Integer>       Holder; // was auto-generated
        typedef super                      Object; // was auto-generated

        // was auto-generated
        static Integer::Handle create(const int32_t& n)
            {
            return new Integer(n);
            }

    protected:
        Integer(int32_t n)
            : super(), m_n(n)
            {
            }

        Integer(const Integer& that)
            : super(that), m_n(that.n)
            {
            }

    public:
        virtual int32_t getValue() const
            {
            return m_n;
            }

        virtual int32_t compareTo(Object::View v) const
            {
            return getValue() - cast<Integer::View>(v)->getValue();
            }

        virtual void toStream(std::ostream& out) const
            {
            out << getValue();
            }

        // was auto-generated
        virtual Object::Handle clone() const
            {
            return new Integer(*this);
            }

        // was auto-generated
        virtual size32_t sizeOf() const
            {
            return ::sizeof(Integer);
            }

    private:
        int32_t m_n;
    };

Example 2-14 illustrates using the spec'd class:

Example 2-14 Using specs to Define a Class

Integer::Handle hNum1 = Integer::create(123);
Integer::Handle hNum2 = Integer::create(456);

if (hNum1->compareTo(hNum2) > 0)
    {
    std::cout << hNum1 << " is greater then " << hNum2 << std::endl;
    }

2.2.2 Equality, Hashing, Cloning, Immutability, and Serialization

What do all these concepts have in common? They all identify the state of an object, and as such will generally have similar implementation concerns. Simply put all data members referenced in one of these methods, will likely need to be referenced in all of the methods. Conversely any data members which are not referenced by one, should likely not be referenced by any of these methods. Consider the simple case of a HashSet::Entry, which contains the well known key and value data members. Certainly these are to be considered in the equals method, and would likely be tested for equality by using a call to their own equals method, rather than through reference equality. Now what if this Entry also contains as part of the implementation of the HashSet a handle to the next Entry within the HashSet's bucket, and perhaps also contains a handle back to the HashSet itself. Should these be considered in equals as well? Likely not, it would seem reasonable that comparing two entries consisting of equal keys and values, from two maps should be considered equal. Following this line of thought the hashCode method on Entry would completely ignore data members except for key and value, and the Entry's hashCode would be computed using the results of its key and value hashCode, rather then using their identity hashCode. that is, a deep equality check in equals implies a deep hash in hashCode. Moving onto clone it can be seen that in cloning an Entry, we would not want to clone all its data member, but only the key and value. Obviously cloning the parent Map as part of clone the Entry would make no sense, and a similar argument can be made for cloning the handle to the next Entry. This line of thinking can be extended to the isImmutable method, and to serialization as well. While it is certainly not hard and fast rule it is worth considering this approach when implementing any of these methods.

2.2.3 Threading

The object model includes managed threads, which allows for easy creation of platform independent, multi-threaded, applications. The threading abstraction includes support for creating, interrupting, and joining threads. Thread local storage is available from the coherence::lang::ThreadLocal class. Thread dumps are also available for diagnostic and troubleshooting purposes. The managed threads are ultimately wrappers around the system's native thread type, such as POSIX or Windows Threads. This threading abstraction is used internally by Coherence, but is available for the application, if necessary.

Example 2-15 illustrates how to create a new Runnable instance and spawn a thread:

Example 2-15 Creating a Runnable Instance and Spawning a Thread

class HelloRunner
      : public class_spec<HelloRunner,
          extends<Object>,
          implements<Runnable> >
     {
     friend class factory<HelloRunner>;

     protected:
          HelloRunner(int cReps)
              : super(), m_cReps(cReps)
              {
              }

     public:
          virtual void run()
              {
              for (int i = 0; i < m_Reps; ++i)
                  {
                  Thread::sleep(1000);
                  std::cout << "hello world" << std::endl;
                  }
              }

      protected:
          int m_cReps;
      };

...

Thread::Handle hThread = Thread::create(HelloRunner::create(10));
hThread->start();
hThread->join();

Refer to coherence::lang::Thread and coherence::lang::Runnable for more information.

2.2.4 Weak References

The primary functional limitation of a reference counting scheme is automatic cleanup of cyclical object graphs. Consider the simple bi-directional relationship illustrated in Figure 2-1.

Figure 2-1 A Bi-Directional Relationship

Description of Figure 2-1 follows
Description of "Figure 2-1 A Bi-Directional Relationship"

In this picture, both A and B have a reference count of one, which keeps them active. What they don't realize is that they are the only things keeping each other active, and that no external references to them exist. Reference counting alone in unable to handle these self sustaining graphs, and memory would be leaked.

The provided mechanism for dealing with graphs is weak references. A weak reference is one which will reference an object, but not prevent it from being deleted. As illustrated in Figure 2-2, the A->B->A issue could be resolved by changing it to the following.

Figure 2-2 Establishing a Weak Reference

Description of Figure 2-2 follows
Description of "Figure 2-2 Establishing a Weak Reference"

Where A now has a weak reference to B. If B were to reach a point where it was only referenced weakly, it would clear all weak references to itself and then be deleted. In this simple example that would also trigger the deletion of A, as B had held the only reference to A.

Weak references allow for construction of more complicated structures then this. But it becomes necessary to adopt a convention for which references are weak and which are strong. Consider a tree illustrated in Figure 2-3. The tree consists of nodes A, B, C; and two external references to the tree X, and Y.

Figure 2-3 Weak and Strong References to a Tree

Description of Figure 2-3 follows
Description of "Figure 2-3 Weak and Strong References to a Tree"

In this tree parent (A) use strong references to children (B, C), and children use weak references to their parent. With the picture as it is, reference Y could navigate the entire tree, starting at child B, and moving up to A, and then down to C. But what if reference X were to be reset to NULL? This would leave A only being weakly referenced and it would clear all weak references to itself, and be deleted. In deleting itself there would no longer be any references to C, which would also be deleted. At this point reference Y, without having taken any action would now refer to the situation illustrated in Figure 2-4.

Figure 2-4 Artifacts after Deleting the Weak References

Description of Figure 2-4 follows
Description of "Figure 2-4 Artifacts after Deleting the Weak References"

This is not necessarily a problem, just a possibility which must be considered when using weak references. To work around this issue, the holder of Y would also likely need to maintain a reference to A to ensure the tree did not dissolve away unexpectedly.

See the Javadoc for coherence::lang::WeakReference, WeakHandle, and WeakView for usage details.

2.2.5 Virtual Constructors

As is typical in C++, referencing an object under construction can be dangerous. Specifically references to this are to be avoided within a constructor, as the object initialization has not yet completed. For managed objects, creating a handle to this from the constructor will in most cases cause the object to be destructed before it ever finishes being created. To address this, the object model includes support for virtual constructors. The virtual constructor onInit is defined by Object and can be overridden on derived classes. This method is called automatically by the object model just after construction completes, and just before the new object is returned from its static create method. Within the onInit method it is safe to reference this, to call virtual functions, and to hand out references to the new object to other class instances. Any derived implementation of onInit must include a call to super::onInit() to allow the parent class to also initialize itself.

2.2.6 Advanced Handle Types

In addition to the Handle and View smart pointers (discussed previously), the object model contains several other specialized variants that can be used. For the most part use of these specialized smart pointers is limited to writing new managed classes, and they will not show up in normal application code.

Table 2-1 Advanced Handle Types Supported by Coherence for C++

Type Thread-safe? View Notes

coherence:lang:TypedHandle<T>

No

Conditional on T

The implementation of Handle and View

coherence:lang:BoxHandle<T>

No

Conditional on T

Allows automatic creating of managed objects from primitive types.

coherence:lang:TypedHolder<T>

No

May

May act as a Handle or a View. Basic types stored in collections

coherence:lang:Immutable<T>

No

Yes

Ensures const-ness of referring object.

coherence:lang:WeakHandle<T>

Yes

No

Does not prevent destruction of referring object.

coherence:lang:WeakView<T>

Yes

Yes

Does not prevent destruction of referring object.

coherence:lang:MemberHandle<T>

Yes

No

Transfers const-ness of enclosing object.

coherence:lang:MemberView<T>

Yes

Yes

Thread-safe View.

coherence:lang:MemberHolder<T>

Yes

May

May act a thread-safe Handle or View.


2.2.7 Thread Safety

Although the object model includes a thread-safe reference count, this does not provide automatic thread safety for the state of derived classes. As is typical it is up to each individual class implementation to choose to provide for higher level of thread safety. Regardless of the presence or lack of higher level thread-safety, the reference count remains thread-safe.

2.2.8 Synchronization and Notification

Every Object in the object model can be a point of synchronization and notification. To synchronize an object and acquire its internal monitor, use a COH_SYNCHRONIZED macro code block, as shown in Example 2-16:

Example 2-16 A Sample COH_SYNCHRONIZED Macro Code Block

SomeClass::Handle h = getObjectFromSomewhere();

COH_SYNCHRONIZED (h)
    {
    // monitor of Object referenced by h has been acquired

    if (h->checkSomeState())
        {
        h->actOnThatState();
        }
    } // monitor is automatically released

The COH_SYNCHRONIZED block performs the monitor acquisition and release. You can safely exit the block with return, throw, COH_THROW, break, continue, and goto statements.

The Object class includes wait(), wait(timed), notify(), and notifyAll() methods for notification purposes. To call these methods, the caller must have acquired the objects's monitor. Refer to coherence::lang::Object for details.

Read-write locks are also provided see coherence::util::ThreadGate for details.

2.2.9 Thread Safe Handles

The Handle, View, and Holder inner types defined on managed classes are not thread-safe. That is it is not safe without some form of synchronization to have multiple threads use the same handle if any of them may change the handle to reference another object. There is an important distinction here, we are discussing the thread-safety of the handle, not the object referenced by the handle.

This lack of thread-safety is an performance optimization and assumes that the vast majority of handles are stack allocated. Stack allocated handles, with very few provisos are by their very nature thread-safe. The provisos are that it is not a static or global variable, and that references to the handle are not shared with other threads.

There are then two types of code which must account for thread-safety of handles.

  • Managed class implementations. It should be assumed that any instance of a managed class may be shared by multiple threads. Though this may not be strictly true, if a handle to the object is supplied to code outside of your control (for instance put into a cache), there is no guarantee that the object will not made be visible to other threads.

  • Non-managed multi-threaded application code.

There are optimizations in place for the first case, namely the special thread-safe handle types:

These handle types may be read and written from multiple thread without the need for additional synchronization. They are primarily intended for use as the data-members of other managed classes, and they make use of their parent's internal atomic state to provide thread-safety. When using these handle types it is recommended that they be read into a normal stack-based handle if they will be accessed more then once within a code block. This assignment to a normal stack-based handle is thread-safe, and once completed allows for essentially free dereferencing of the stack-based handle. Note that when initializing thread-safe handles a reference to the parent class must be supplied as the first parameter, this reference can be obtained by calling self() on the parent object.

Example 2-17 illustrates a trivial example of such a usage:

Example 2-17 Thread-safe Handle

class Employee
    : public class_spec<Employee>
    {
    friend class factory<Employee>;

    protected:
        Employee(String::View vsName, int32_t nId)
            : super(), m_vsName(self(), vsName), m_nId(nId)
            {
            }

    public:
        String::View getName() const
            {
            return m_vsName; // read is automatically thread-safe
            }

        void setName(String::View vsName)
            {
            m_vsName = vsName; // write is automatically thread-safe
            }

        int32_t getId() const
            {
            return m_nId;
            }          

    private:
        MemberView<String>    m_vsName;
        const int32_t         m_nId;
    };

The same basic technique can be applied to non-managed classes as well. This is made possible, by using the defined synchronization point as the "parent" for the thread-safe handles. With this approach you must ensure that the "parent" m_hMutex Object outlives the thread-safe handle m_vsName. This is easily accomplished by declaring them as data-members of the same class, and declaring the "parent" before the handle. This is illustrated in Example 2-18:

Example 2-18 Thread-safe Handle as a Non-Managed Class

class Employee
    {
    public:
        Employee(String::View vsName, int32_t nId)
            : m_hMutex(Object::create()), m_vsName(*m_hMutex, vsName), m_nId(nId)
            {
            }

    public:
        String::View getName() const
            {
            return m_vsName;
            }

        void setName(String::View vsName)
            {
            m_vsName = vsName;
            }

        int32_t getId() const
            {
            return m_nId;
            }          

    private:
        const Object::Handle m_hMutex;
        MemberView<String>   m_vsName;
        const int32_t        m_nId;
    };

2.3 Diagnostics and Troubleshooting

This section provides information which can aid in diagnosing issues in applications which make use of the object mode.

2.3.1 Thread Dumps

Thread dumps are available for diagnostic and troubleshooting purposes. These thread dumps also include the stack trace. You can generate a thread dump by performing a CTRL+BREAK (Windows) or a CTRL+BACKSLASH (UNIX). Example 2-19 illustrates a sample thread dump:

Example 2-19 Sample Thread Dump

Thread dump Oracle Coherence for C++ v3.4b397 (Pre-release) (Apple Mac OS X x86 debug) pid=0xf853; spanning 190ms

"main" tid=0x101790 runnable: <native>
    at coherence::lang::Object::wait(long long) const
    at coherence::lang::Thread::dumpStacks(std::ostream&, long long)
    at main
    at start

"coherence::util::logging::Logger" tid=0x127eb0 runnable: Daemon{State=DAEMON_RUNNING, Notification=false, 
StartTimeStamp=1216390067197, WaitTime=0, ThreadName=coherence::util::logging::Logger}
    at coherence::lang::Object::wait(long long) const
    at coherence::component::util::Daemon::onWait()
    at coherence::component::util::Daemon::run()
    at coherence::lang::Thread::run()

2.3.2 Memory Leak Detection

While the managed object model reference counting helps to prevent memory leaks they are still possible. The most common way in which they are triggered is through cyclical object graphs. The object model includes heap analysis support to help identify if leaks are occurring, by tracking the number of live objects in the system. Comparing this value over time provides a simple means of detecting if the object count is consistently increasing, and thereby likely leaking. Once a probable leak has been detected, the heap analyzer can help track it down as well, by provided statistics on what types of objects appeared to have leaked.

Coherence provides a pluggable coherence::lang::HeapAnalyzer interface. The HeapAnalyzer implementation can be specified by using the tangosol.coherence.heap.analyzer system property. The property can be set to one of the following values:

  • none—No heap analysis will be performed.

  • object—The coherence::lang::ObjectCountHeapAnalyzer will be used. It provides simple heap analysis based solely on the count of the number of live objects in the system. This is the default analyzer.

  • class—The coherence::lang::ClassBasedHeapAnalyzer will be used. It provides heap analysis at the class level, that is it tracks the number of live instances of each class, and the associated byte level usage.

  • custom—Lets you define your own analysis routines. You specify the name of a class registered with the SystemClassLoader.

Heap information is returned when you perform a CTRL+BREAK (Windows) or CTRL+BACKSLASH (UNIX). The following is an example of heap analysis information is returned by the class based analyzer:

Example 2-20 Data Returned by a Heap Analyzer

Space           Count           Class
44 B            1               coherence::lang::Integer32
70 B            1               coherence::lang::String
132 B           1               coherence::util::SafeHashMap::Entry

Total: 246 B, 3 objects, 3 classes

The above example was the heap analysis delta resulting from the insertion of a new entry into a Map.

2.3.3 Memory Corruption Detection

For all that the object model does to prevent memory corruption, it will typically be used along side non-managed code which could cause corruption. To combat this, the object model includes memory corruption detection support. When enabled, the object model's memory allocator will pad the beginning and end of each object allocation by a configurable number of pad bytes. This padding is encoded with a pattern which can later be validated to ensure that the pad has not been touched. If memory corruption occurs, and hits one of the pads, subsequent validations will detect the corruption. Validation is performed when the object is destroyed.

The debug version of the Coherence C++ API has padding enabled by default, using a pad size of 2*(word size), on each side of an object allocation. In a 32-bit build, this adds 16 bytes per object. Increasing the size of the padding will increase the chances of corruption hitting a pad, and thus the chance of detecting corruption.

The size of the pad can be configured by using the tangosol.coherence.heap.padding system property, which can be set to the number of bytes for the pre/post pad. Setting this system property to a non-zero value will enable the feature, and is available even in release builds.

Example 2-21 illustrates the results from an instance of memory corruption detection:

Example 2-21 Results from a Memory Corruption Run

Error during ~MemberHolder: coherence::lang::IllegalStateException: memory corruption detected in 5B post-padding at offset 4 of memory allocated at 0x132095