9 Using the Coherence C++ Object Model

Learn how to use the C++ object model on which Coherence for C++ is built.

This chapter includes the following sections:

9.1 Using the Object Model

Coherence C++ clients use the C++ object model which provides a standard approach to building Coherence applications.

This section includes the following topics:

9.1.1 Coherence Namespaces

This coherence namespace contains the following general purpose namespaces:

  • coherence::lang—the essential classes that comprise 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. As a best practice include, at a minimum, coherence/lang.ns in code that uses this object model.

9.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.

9.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.

This section includes the following topics:

9.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 are 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.

9.1.3.2 Using handles

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

This section includes the following topics:

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.

Dereferencing handles

When dereferencing a handle that references NULL, the system throws 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;

9.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 creates a new instance of a string:

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

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

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

9.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.

This section includes the following topics:

9.1.4.1 String Instantiation

String objects can easily be constructed from char* or std::string strings. For example:

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.

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

9.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. Auto-boxing allows the code shown in the prior samples to be rewritten:

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

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

9.1.5 Type Safe Casting

Handles are type safe, in the following example, the compiler does 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; // does not compile

However, the following example does compile, as all Strings are Objects.

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

This section includes the following topics:

9.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 (run-time 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:

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

The cast<H> function throws a coherence::lang::ClassCastException if the supplied object was not of the expected type. The instanceof<H> function tests 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. For example:

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
  {
  ...
  }

9.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:

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 defined to ObjectArray for easier readability.

9.1.7 Collection Classes

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

  • coherence::util::Collection —interface

  • coherence::util::List—interface

  • coherence::util::Set—interface

  • coherence::util::Queue—interface

  • coherence::util::Map—interface

  • coherence::util::Arrays—implementation

  • coherence::util::LinkedList—implementation

  • coherence::util::HashSet—implementation

  • coherence::util::DualQueue—implementation

  • coherence::util::HashMap—implementation

  • coherence::util::SafeHashMap—implementation

  • coherence::util::WeakHashMap—implementation

  • coherence::util::IdentityHashMap—implementation

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. For example:

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

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

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

9.1.8 Managed Exceptions

In the object model, exceptions are also managed objects. Managed Exceptions allow caught exceptions to be held 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 sets stack information and then calls 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:

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&.

9.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 run-time 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 when immutable, an object cannot revert to being mutable. You cannot cast away const-ness to turn a View into a Handle as this would violate the proved immutability.

Immutability is important with 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.

9.1.10 Integrating Existing Classes into the Object Model

Frequently, existing classes must be integrated into the object model. A typical example would be 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 automatically converts a non-managed plain old C++ class instance into a managed class instance at run time.

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.

9.2 Writing New Managed Classes

You can write new managed classes, which are classes that extend the Object class.
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.

Note:

For Microsoft Visual Studio 2017, Coherence managed classes cannot be declared in an anonymous namespace. There are two helper macros for using pseudo anonymous namespaces: COH_OPEN_NAMESPACE_ANON(NAME) and COH_CLOSE_NAMESPACE_ANON. Further details on the macros are provided in compatibility.hpp.

This section includes the following topics:

9.2.1 Specification-Based Managed Class Definition

Specification-based definitions (specs) enable 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 the specs above. 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 for 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.

The following example illustrates using interface_spec to define a Comparable interface:

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

The following example illustrates using interface_spec to define a derived interface Number:

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

The following example uses cloneable_spec to produce an implementation.

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.

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 can also be defined without the use of specs. For example:

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;
    };

The following example illustrates using the spec'd 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;
    }

9.2.2 Equality, Hashing, Cloning, Immutability, and Serialization

Equality, Hashing, Cloning, Immutability, and Serialization all identify the state of an object and generally have similar implementation concerns. Simply put, all data members referenced in one of these methods, are likely 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. 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. If 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 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.

For clone, only the key and value (not all the data members) require cloning. To clone 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 a hard and fast rule, it is worth considering this approach when implementing any of these methods.

9.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::ThreadLocalreference 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.

The following example illustrates how to create a Runnable instance and spawn 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.

9.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 9-1.

Figure 9-1 A Bi-Directional Relationship

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

In this picture, both A and B have a reference count of one, which keeps them active. What they do not realize is that they are the only things keeping each other active, and that no external references to them exist. Reference counting alone is 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 references an object, but not prevent it from being deleted. As illustrated in Figure 9-2, the A->B->A issue could be resolved by changing it to the following.

Figure 9-2 Establishing a Weak Reference

Description of Figure 9-2 follows
Description of "Figure 9-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 9-3. The tree consists of nodes A, B, C; and two external references to the tree X, and Y.

Figure 9-3 Weak and Strong References to a Tree

Description of Figure 9-3 follows
Description of "Figure 9-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 9-4.

Figure 9-4 Artifacts after Deleting the Weak References

Description of Figure 9-4 follows
Description of "Figure 9-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 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.

9.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 usually causes the object to be destructed before it ever finishes being created. Instead, 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.

9.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 do not appear in normal application code.

Table 9-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:WeakHolder<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.

coherence:lang:FinalHandle<T>

Yes

No

Thread-safe const transferring read-only Handle.

coherence:lang:FinalView<T>

Yes

Yes

Thread-safe read-only View.

coherence:lang:FinalHolder<T>

Yes

May

May act a thread-safe read-only Handle or View.

9.2.7 Thread Safety

Although the base Object class is thread-safe, this cannot provide automatic thread safety for the state of derived classes. As is typical it is up to each individual derived class implementation to provide for higher level thread-safety. The object model provides some facilities to aid in writing thread-safe code.

This section includes the following topics:

9.2.7.1 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. For example:

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.

9.2.7.2 Thread Safe Handles

The Handle, View, and Holder nested types defined on managed classes are intentionally not thread-safe. That is it is not safe to have multiple threads share a single handle. There is an important distinction here: thread-safety of the handle is being discussed not the object referenced by the handle. It is safe to have multiple distinct handles that reference the same object from different threads without additional synchronization.

This lack of thread-safety for these handle types offers a significant performance optimization as the vast majority of handles are stack allocated. So long as references to these stack allocated handles are not shared across threads, there is no thread-safety issue to be concerned with.

Thread-safe handles are needed any time a single handle may be referenced by multiple threads. Typical cases include:

  • Global handles - using the standard handle types as global or static variable is not safe.

  • Non-managed multi-threaded application code - Use of standard handles within data structures which may be shared across threads is unsafe.

  • Managed classes with handles as data members - It should be assumed that any instance of a managed class may be shared by multiple threads, and thus using standard handles as data members is unsafe. Note that while it may not be strictly true that all managed classes may be shared across threads, if an instance is passed to code outside of your explicit control (for instance put into a cache), there is no guarantee that the object is not visible to other threads.

The use of standard handles should be replaced with thread-safe handles in such cases. The object model includes the following set of thread-safe handles.

  • coherence::lang::MemberHandle<T>—thread-safe version of T::Handle

  • coherence::lang::MemberView<T>—thread-safe version of T::View

  • coherence::lang::MemberHolder<T>—thread-safe version of T::Holder

  • coherence::lang::FinalHandle<T>—thread-safe final version of T::Handle

  • coherence::lang::FinalView<T>—thread-safe final version of T::View

  • coherence::lang::FinalHolder<T>—thread-safe final version of T::Holder

  • coherence::lang::WeakHandle<T>—thread-safe weak handle to T

  • coherence::lang::WeakView<T>—thread-safe weak view to T

  • coherence::lang::WeakHolder<T>—thread-safe weak T::Holder

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, each instance is provided with a reference to a guardian managed Object. The guardian's internal thread-safe atomic state is used to provide thread-safety to the handle. When using these handle types it is recommended that they be read into a normal stack based handle if they are continually accessed within a code block. This assignment to a standard stack based handle is thread-safe, and, after completed, allows for essentially free dereferencing of the stack based handle. Note that when initializing thread-safe handles a reference to a guardian Object must be supplied as the first parameter, this reference can be obtained by calling self() on the enclosing object.

The following example demonstrates a 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. Since non-managed classes do not extend coherence::lang::Object, they cannot be used as the guardian of thread-safe handles. It is possible to use another Object as the guardian. However, it is crucial to ensure that the guardian Object outlives the guarded thread-safe handle. When using another object as the guardian, obtain a random immortal guardian from coherence::lang::System through a call to System::common(). For example:

class Employee
    {
    public:
        Employee(String::View vsName, int32_t nId)
            : m_vsName(System::common(), 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:
         MemberView<String> m_vsName;
         const int32_t m_nId;
    };

When writing managed classes it is preferable to obtain a guardian through a call to self() then to System::common().

Note:

In the rare case that one of these handles is declared through the mutable keyword, it must be informed of this fact by setting fMutable to true during construction.

Thread-safe handles can also be used in non-class shared data as well. For example, global handles:

MemberView<NamedCache> MY_CACHE(System::common());

int main(int argc, char** argv)
    {
    MY_CACHE = CacheFactory::getCache(argv[0]);
    }

9.2.7.3 Escape Analysis

The object model includes escape analysis based optimizations. The escape analysis is used to automatically identify when a managed object is only visible to a single thread and in such cases optimize out unnecessary synchronizations. The following types of operations are optimized for non-escaped objects.

  • reference count updates

  • COH_SYNCHRONIZED acquisition and release

  • reading/writing of thread-safe handles

  • reading of thread-safe handles from immutables

Escape analysis is automatic and is completely safe so long as you follow the rules of using the object model. Most specifically is that it is not safe to pass a managed object between threads without using a provided thread-safe handle. Passing it by an external mechanism does not allow escape analysis to identify the "escape" which could cause memory corruption or other run-time errors.

This section includes the following topics:

9.2.7.3.1 Shared handles

Each managed class type includes nested definitions for a Handles, View, and Holder. These handles are used extensively throughout the Coherence API, and is application code. They are intended for use as stack based references to managed objects. They are not intended to be made visible to multiple threads. That is a single handle should not be shared between two or more threads, though it is safe to have a managed Object referenced from multiple threads, so long as it is by distinct Handles, or a thread-safe MemberHandle/View/Holder.

It is important to remember that global handles to managed Objects should be considered to be "shared", and therefore must be thread-safe, as demonstrated previously. The failure to use thread-safe handles for globals causes escaped objects to not be properly identified leading to memory corruption.

In 3.4 these non thread-safe handles could be shared across threads so long as external synchronization was employed, or if the handles were read-only. In 3.5 and later this is no longer true, even when used in a read-only mode or enclosed within external synchronization these handles are not thread-safe. This is due to a fundamental change in implementation which drastically reduces the cost of assigning one handle to another, which is an operation which occurs constantly. Any code which was using handles in this fashion should be updated to make use of thread-safe handles. See Thread Safe Handles.

9.2.7.3.2 Const Correctness

Coherence escape analysis, among other things, leverages the computed mutability of an object to determine if state changes on data members are still possible. Namely, when an object is only referenced from views, it is assumed that its data members do not undergo further updates. The C++ language provides some mechanisms to bypass this const-only access and allow mutation from const methods. For instance, the use of the mutable keyword in a data member declaration, or the casting away of constness. The arguably cleaner and supported approach for the object model is the mutable keyword. For the Coherence object model, when a thread-safe data member handle is declared as mutable this information must be communicated to the data member. All thread-safe data members support an optional third parameter fMutable which should be set to true if the data member has been declared with the mutable keyword. This informs the escape analysis routine to not consider the data member as "const" when the enclosing object is only referenced using Views. Casting away of the constness of managed object is not supported, and can lead to run time errors if the object model believes that the object can no longer undergo state changes.

9.2.7.4 Thread-Local Allocator

Coherence for C++ includes a thread-local allocator to improve performance of dynamic allocations which are heavily used within the API. By default, each thread grows a pool to contain up to 64KB of reusable memory blocks to satisfy the majority of dynamic object allocations. The pool is configurable using the following system properties:

  • coherence.heap.slot.size controls the maximum size of an object which is considered for allocation from the pool, the default is 128 bytes. Larger objects call through to the system's malloc routine to obtain the required memory.

  • coherence.heap.slot.count controls the number of slots available to each thread for handling allocations, the default is 512 slots. If there are no available slots, allocations fall back on malloc.

  • coherence.heap.slot.refill controls the rate at which slots misses trigger refilling the pool. The default of 10000 causes 1/10000 pool misses to force an allocation which is eligible for refilling the pool.

The pool allocator can be disabled by setting the size or count to 0.

9.3 Diagnostics and Troubleshooting

Learn how to diagnosing issues in applications that use the Coherence C++ object model.

This section includes the following topics:

9.3.1 Thread-Local Allocator Logs

Logs can be enabled to view the efficiency of the thread-local allocator pool. To enable the logs, set the coherence.heap.logging system property to true.

The log entries indicate the memory location of the pool, the size of the pool, how many allocation areas are in the pool and the fraction of successful hits on the pool (the rate of finding a slot within the pool). The following example demonstrates a typical allocator log entry:

(thread=main): Allocator hit: pool=0x7f8e5ac039d0, size=128, slots=512, hit rate=0.62963

9.3.2 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). The following output illustrates a 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()

9.3.3 Memory Leak Detection

While the managed object model reference counting helps 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. After 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 coherence.heap.analyzer system property. The property can be set to the following values:

  • none—No heap analysis is performed. This is the default.

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

  • class—The coherence::lang::ClassBasedHeapAnalyzer is 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.

  • alloc —Specialization of coherence::lang::ClassBasedHeapAnalyzer which additionally tracks the allocation counts at the class level.

  • 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 output illustrates heap analysis information returned by the class-based analyzer. It returns the heap analysis delta resulting from the insertion of a new entry into a Map.

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

9.3.4 Memory Corruption Detection

For all that the object model does to prevent memory corruption, it is typically used along side non-managed code which could cause corruption. Therefore, the object model includes memory corruption detection support. When enabled, the object model's memory allocator pads 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 affects a pad, subsequent validations 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 increases the chances of corruption affecting a pad, and thus the chance of detecting corruption.

The size of the pad can be configured by using the 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 nonzero value enables the feature, and is available even in release builds.

The following output illustrates the results from an instance of memory corruption detection:

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

9.4 Application Launcher - Sanka

Coherence uses an application launcher for invoking executable classes embedded within a shared library. The launcher allows for a shared library to contain utility or test executables without shipping individual standalone executable binaries.

This section includes the following topics:

9.4.1 Command line syntax

The launcher named sanka works similar to java, in that it is provided with one or more shared libraries to load, and a fully qualified class name to execute.

ge: sanka [-options] <native class> [args...]
 
available options include:
    -l <native library list>  dynamic libraries to load, separated by : or ;
    -D<property>=<value>      set a system property
    -version                  print the Coherence version
    -?                        print this help message
    <native class>            the fully qualified class. For example,
                              coherence::net::CacheFactory

The specified libraries must either be accessible from the operating system library path (PATH, LD_LIBRARY_PATH, DYLD_LIBRARY_PATH), or they may be specified with an absolute or relative path. Library names may also leave off any operating specific prefix or suffix. For instance the UNIX libfoo.so or Windows foo.dll can be specified simply as foo. The Coherence shared library which the application was linked against must be accessible from the system's library path as well.

9.4.2 Built-in Executables

Several utility executables classes are included in the Coherence shared library:

  • coherence::net::CacheFactory runs the Coherence C++ console

  • coherence::lang::SystemClassLoader prints out the registered managed classes

  • coherence::io::pof::SystemPofContext prints out the registered POF types

The later two executables can be optionally supplied with shared libraries to inspect, in which case they output the registration which exists in the supplied library rather then all registrations.

Note:

The console which was formerly shipped as an example, is now shipped as a built-in executable class.

9.4.3 Sample Custom Executable Class

Applications can of course still be made executable in the traditional C++ means using a global main function. If desired you can make your own classes executable using Sanka as well. The following is a simple example of an executable class:

#include "coherence/lang.ns"
 
COH_OPEN_NAMESPACE2(my,test)
 
using namespace coherence::lang;
 
class Echo
    : public class_spec<Echo>
    {
    friend class factory<Echo>;
 
    public:
        static void main(ObjectArray::View vasArg)
            {
            for (size32_t i = 0, c = vasArg->length; i < c; ++i)
                {
                std::cout << vasArg[i] << std::endl;
                }
            }
    };
COH_REGISTER_EXECUTABLE_CLASS(Echo); // must appear in .cpp
 
COH_CLOSE_NAMESPACE2

As you can see the specified class must have been registered as an ExecutableClass and have a main method matching the following signature:

static void main(ObjectArray::View)

The supplied ObjectArray parameter is an array of String::View objects corresponding to the command-line arguments which followed the executable class name.

When linked into a shared library, for instance libecho.so or echo.dll, the Echo class can be run as follows:

> sanka -l echo my::test::Echo Hello World
Hello
World