This page details the following enhancements to SLEE APIs, which Rhino provides for SLEE applications:

On this page...

SbbContext interface extensions

Rhino provides an extension to the standard javax.slee.SbbContext interface with the com.opencloud.rhino.slee.RhinoSbbContext interface. The RhinoSbbContext interface provides additional functionality to SBBs running in Rhino. The RhinoSbbContext interface is as follows:

package com.opencloud.rhino.slee;

import java.util.Map;
import javax.slee.SLEEException;
import javax.slee.Sbb;
import javax.slee.SbbContext;
import javax.slee.TransactionRequiredLocalException;
import javax.slee.TransactionRolledbackLocalException;
import com.opencloud.rhino.cmp.CMPFields;
import com.opencloud.rhino.cmp.Encodable;
import com.opencloud.rhino.cmp.codecs.DatatypeCodec;
import com.opencloud.rhino.cmp.codecs.DecoderUtils;
import com.opencloud.rhino.cmp.codecs.EncoderUtils;
import com.opencloud.rhino.facilities.Tracer;
import com.opencloud.rhino.facilities.childrelations.ChildRelationFacility;
import com.opencloud.rhino.slee.environment.JndiBinding;

public interface RhinoSbbContext extends SbbContext {
    public String getConvergenceName()
        throws TransactionRolledbackLocalException, IllegalStateException, SLEEException;

    public Tracer getTracer(String tracerName)
        throws NullPointerException, IllegalArgumentException, SLEEException;

    public RhinoActivityContextInterface[] getActivities()
        throws TransactionRequiredLocalException, IllegalStateException, SLEEException;

    public RhinoActivityContextInterface[] getActivities(Class<?> type)
        throws NullPointerException, TransactionRequiredLocalException, IllegalStateException, SLEEException;

    public ChildRelationFacility getChildRelationFacility()
        throws SLEEException;

    public CMPFields getCMPFields()
        throws SLEEException;

    public Map<String,JndiBinding> getJndiBindings()
        throws SLEEException;

    public <T> void setServiceContext(T context)
        throws SLEEException;

    public <T> T getServiceContext()
        throws SLEEException;

    public <T> void setEncodableContext(T context)
        throws SLEEException;

    public void enableEntityTreePersistence()
        throws TransactionRequiredLocalException, SLEEException;

    public ConvergenceNameSessionOwnershipRecord getConvergenceNameSessionOwnershipRecord()
        throws TransactionRequiredLocalException, IllegalStateException, SLEEException;

    public ConvergenceNameSessionOwnershipRecord getConvergenceNameSessionOwnershipRecord(long ttl)
        throws TransactionRequiredLocalException, IllegalStateException, SLEEException;
}

RhinoSbbContext interface getConvergenceName method

The getConvergenceName method returns the convergence name that the SBB entity was created with. The value returned from this method is a vendor-specific string that uniquely identifies the initial event selector conditions that led to the SBB entity’s creation.

This method only returns a non-null value if invoked on a RhinoSbbContext object belonging to a root SBB entity.

RhinoSbbContext interface getTracer method

The getTracer method overrides the same method from SbbContext to return a Rhino-specific extension of the Tracer interface.

Tip For more about this Tracer extension, please see Tracer extensions.

RhinoSbbContext interface getActivities methods

The RhinoSbbContext interface defines two getActivities methods:

  • getActivities() — This method overrides the same method from SbbContext to return a Rhino-specific extension of the ActivityContextInterface interface. This ActivityContextInterface extension is described in more detail below. Otherwise, this method behaves in the same way as defined by the JAIN SLEE specification.

  • getActivities(Class) — This method behaves similarly to the no-argument version; however it only returns activity context objects where the type of the underlying activity object is assignable to the class argument. For example, if this method was invoked with NullActivity.class as an argument, then only activity context objects for the null activities the SBB entity is attached to would be returned.

RhinoSbbContext interface getChildRelationFacility method

The getChildRelationFacility returns a Child Relation Facility object for the SBB.

Tip For more about the Child Relation Facility, please see Child Relation Facility

RhinoSbbContext interface getCMPFields method

The getCMPFields method provides the SBB with access to its per-instance state.

Tip For details on the CMPFields object returned from this method, please see The CMPFields interface.

RhinoSbbContext interface getJndiBindings method

The getJndiBindings method returns a map describing the JNDI bindings available to the SBB.

Tip For more about this method, please see JNDI environment

RhinoSbbContext interface setServiceContext and getServiceContext methods

The setServiceContext and getServiceContext methods allow setting and retrieving the service context object for the SBB. The service context provides an alternative storage mechanism to using static class fields for arbitrary, typically constant data that the SBB wants to share between SBB objects in the same service. The use of static class fields to store shared data becomes problematic when the encapsulating class resides in a library component jar rather than an SBB jar. In this case the static fields end up being shared between all uses of that library, rather than being scoped to a single service. The service context provides similar functionality to that of a static class field, but it is guaranteed to have visibility only within the service.

A typical use of the service context is to store data calculated by the SBB during the SBB Service Lifecycle Callbacks service lifecycle callback methods.

Any object may be stored in the service context. The service context object is stored by reference, and is never serialised. As the service context object may be accessed concurrently by different SBB objects, care must be taken that the service context object provides thread-safe access where necessary.

A service context object will persist across service deactivation and reactivation cycles unless the SBB explicitly resets the service context to null at the appropriate time.

The visibility of a service context object is scoped to a single SBB type within a service. All SBB objects of the same SBB type share the same service context. Different SBB types within the same service may each store their own service context object without conflict.

RhinoSbbContext interface setEncodableContext method

The setEncodableContext method sets the encodable context for the SBB.

Tip For more about encodable contexts, please see Encodable context.

RhinoSbbContext interface enableEntityTreePersistence method

The enableEntityTreePersistence method enables persistence of application state to external replicated storage resources such as a key/value store. Initial replicated persistence of application state can be disabled using the service-properties element in the extension service deployment descriptor, then enabled using this method when the SBB entity has reached a stable state.

Tip For more about application initiated persistence, please see Application initiated persistence.

RhinoSbbContext interface getConvergenceNameSessionOwnershipRecord methods

These methods obtain a reference to the convergence name session ownership record for the SBB entity tree.

Tip For more about convergence name session ownership records, please see Convergence name session ownership record.

Activity context Suspend/Resume Delivery extensions

The JAIN SLEE specification allows delivery of events to be suspended and resumed using the methods defined in the javax.slee.EventContext interface. An obvious restriction to this is that suspending event delivery requires an EventContext object, and the only way to obtain an EventContext object of an unsuspended event is by event handler method argument; therefore an SBB can only suspend delivery of an event that is being delivered to it. An SBB that attaches to multiple activities may at times desire to suspend delivery of events on a selected subset of these activities while waiting for the result of some asynchronous action. The SLEE specification does not provide a trivial solution to this problem, instead requiring a complicated process of suspending events on those activities as they are received, then resuming and processing those events at an appropriate time in a later transaction.

Rhino simplifies this problem by allowing event delivery on any activity context to be suspended and resumed at any time. Rhino defines the com.opencloud.rhino.slee.RhinoActivityContextInterface interface, an extension to javax.slee.ActivityContextInterface, with methods providing this functionality. The RhinoActivityContextInterface interface is shown below:

package com.opencloud.rhino.slee;

import javax.slee.ActivityContextInterface;
import javax.slee.SLEEException;
import javax.slee.TransactionRequiredLocalException;

public interface RhinoActivityContextInterface extends ActivityContextInterface {
    public void suspendDelivery()
        throws IllegalStateException, TransactionRequiredLocalException, SLEEException;

    public void suspendDelivery(int timeout)
        throws IllegalArgumentException, IllegalStateException,
               TransactionRequiredLocalException, SLEEException;

    public void resumeDelivery()
        throws IllegalStateException, TransactionRequiredLocalException, SLEEException;

    public boolean isSuspended()
        throws TransactionRequiredLocalException, SLEEException;
}

All activity context objects that Rhino provides to SBBs implement RhinoActivityContextInterface; therefore a typecast of an activity ccntext object to this interface will always succeed. Rhino will also recognise an event handler method defined with this type, instead of the standard ActivityContextInterface, as the second method argument, removing the need to perform a typecast within the method body if the extensions are required.

An SBB or SBB Part activity context Interface may be declared as extending RhinoActivityContextInterface rather than ActivityContextInterface if desired.

RhinoActivityContextInterface interface suspendDelivery methods

The suspendDelivery methods suspend further delivery of events on the invoked activity context. An activity context is only ever suspended for a specific period of time. The no-argument variant of this method suspends event delivery until some system specific default timeout is reached, while the one-argument variant suspends event delivery for a specific timeout period in milliseconds. The timeout period is measured from the time the suspendDelivery method is invoked. Some time after the timeout period expires, delivery of events on the activity context is automatically resumed. Event delivery can also be manually resumed by an SBB before the timeout period expires, using the resumeDelivery method.

If an SBB suspends delivery of events on an activity context for which it is currently processing an event, then event delivery of that event is suspended in the same way as if suspended using the event context associated with the event.

If an SBB suspends delivery of events on an activity context for which it is not currently processing an event, and the SLEE is already asynchronously delivering an event on that activity context to an SBB, then event delivery suspension of that activity context takes effect after the event handler method invoked on that SBB returns. If the SLEE is not currently delivering an event on that activity context, then event delivery suspension takes immediate effect.

These methods are mandatory transactional methods. The delivery of events fired on the activity context is only suspended if the enclosing transaction commits. If the transaction does not commit, then event delivery will not be suspended.

These methods throw the following exceptions:

Exception When thrown
 java.lang.IllegalArgumentException

The timeout argument is zero or a negative value.

 javax.slee.IllegalStateException

Event delivery has already been suspended on the activity context, either by a suspendDelivery method invocation on the activity context itself or a suspendDelivery method invocation on an EventContext object associated with an event delivered on the activity context.

 javax.slee.TransactionRequiredLocalException

This method is invoked without a valid transaction context.

 javax.slee.SLEEException

Event delivery on the activity context could not be suspended due to a system-level failure.

RhinoActivityContextInterface interface resumeDelivery methods

The resumeDelivery method resumes the delivery of events on the invoked activity context.

This method is a mandatory transactional method. The delivery of events occurring on the activity context is only resumed if the enclosing transaction commits. If the transaction does not commit, then event delivery will not be resumed.

This method throws the following exceptions:

Exception When thrown
 javax.slee.IllegalStateException

Delivery of events on the activity context is not currently suspended; for example, if event delivery has not been suspended or has already been resumed.

 javax.slee.TransactionRequiredLocalException

This method is invoked without a valid transaction context.

 javax.slee.SLEEException

Event delivery on the activity context could not be resumed due to a system-level failure.

RhinoActivityContextInterface interface isSuspended methods

The isSuspended method determines if the delivery of events on the activity context is currently suspended. This method returns true if the event delivery is suspended or false otherwise.

This method is a mandatory transactional method. This method throws the following exceptions:

Exception When thrown
 javax.slee.TransactionRequiredLocalException

This method is invoked without a valid transaction context.

 javax.slee.SleeException

The event delivery status of the activity context could not be determined due to a system-level failure.

Relationship to event context suspend/resume

The event delivery status of an event context is linked to the event delivery status of its associated activity context. If an event context is suspended, then event delivery on the activity context is also suspended, and vice versa. Similarly, if an event context is resumed, then event delivery on the activity context is also resumed. Therefore, an SBB that wants to suspend delivery of events on an activity context for which it is currently processing an event may do so in one of two ways — by invoking a suspendDelivery method on either the event context for the received event or on the activity context that the event was delivered on.

Both these operations have the same effect. Of particular note, after either method has been invoked, both the event context and the activity context will return true from their respective `isSuspended methods.

An SBB may also resume delivery of events on an activity context in one of two ways —  by invoking the resumeDelivery method on either the event context of the suspended event (if the event context is available) or on the activity context.

Again, both these operations will have the same effect; and after either method has been invoked, both the event context and the activity context will return false from their respective isSuspended methods.

SBB local home interface

The JAIN SLEE specification allows an SBB component to declare a local interface. An SBB entity can invoke a target SBB entity in a synchronous manner through the SBB local interface of the target SBB. Rhino also allows an SBB to declare a local home interface. Methods invoked on the local home interface are not specific to any SBB entity and are executed by an SBB object in the Pooled state. An SBB local home object is an instance of a SLEE-implemented class that implements an SBB local home interface.

How to get an SBB local home object

An SBB can only get an SBB local home object for its child SBBs. An SBB can get an SBB local home object for each of its child SBBs using the Child Relation Facility. The getChildSbbLocalHome method defined by the ChildRelationFacility interface returns an SBB local home object for the specified child SBB relation.

The RhinoSbbLocalHome interface

This interface is the base interface of all SBB local home interfaces. Currently Rhino does not allow an SBB to extend this interface; so all SBB local home interfaces, if declared, must be declared as this interface. If an SBB does not declare an SBB local home interface, then the SBB local home interface defaults to RhinoSbbLocalHome.

The RhinoSbbLocalHome interface is shown below:

package com.opencloud.rhino.slee;

import javax.slee.TransactionRequiredLocalException;

public interface RhinoSbbLocalHome {
    public Object verifyConfiguration()
        throws InvalidConfigurationException, TransactionRequiredLocalException;

    public void serviceActivating(Object config)
        throws TransactionRequiredLocalException;

    public void serviceDeactivating()
        throws TransactionRequiredLocalException;
}

If an SBB declares a local home interface in its deployment descriptor, then the local home interface methods must be implemented in the SBB abstract class using a method name constructed by capitalising the first letter of the method name as defined in this interface then prefixing sbbHome. The method parameters and return type must be identical, and the throws clause of the implemented method must be the same as or a subset of the interface method declaration, excluding any runtime exceptions.

Although an SBB that does not declare an SBB local interface receives RhinoSbbLocalHome as a default local home interface, such an SBB is not required to implement the local home interface methods in the SBB abstract class. The SLEE will instead provide a default no-operation implementation of these methods.

SBB service lifecycle callbacks

The RhinoSbbLocalHome interface defines methods that allow an SBB to receive callbacks when a service it is used in is activated or deactivated. These methods are described below.

RhinoSbbLocalHome interface verifyConfiguration method

The SLEE invokes the verifyConfiguration method on the root SBB of a service when the service is about to transition from Inactive to Active. This callback can be used by an SBB, for example, to check that its configuration in environment entries or elsewhere is valid. The SBB can throw an InvalidConfigurationException from this method if a configuration error or other reason means that the service should not be activated at this time.

Child SBBs in the service may also receive this callback method invocation, as described in the Lifecycle callback method invocation cascade section below.

In Rhino, this method is invoked on a service on each cluster node where the service is about to transition to the Active state.

If an SBB declares a local home interface, then a corresponding method with the following signature must be implemented in the SBB abstract class:

public Object sbbHomeVerifyConfiguration() throws InvalidConfigurationException;

The throws clause is optional.

  • If the method throws an InvalidConfigurationException when the administrator has requested activation of the service on a node that is currently operational, then the activation request fails and the service state remains unchanged.

  • If the method throws an InvalidConfigurationException exception when a node (re)starts, where the per-node state for that node indicates that the service should be reactivated, then the reactivation attempt fails, the service transition back to the Inactive state on that node, and an alarm is raised to indicate that the service could not be activated.

The return result from this method is passed as an argument to the serviceActivating method if the service successfully activates. This object can be used, for example, to pass some configuration information calculated during this method to the serviceActivating method if that method would otherwise need to recalculate the same information again.

This method is a mandatory transactional method. When this method is invoked by the SLEE, it is invoked with an active transaction context. If this method is invoked by an SBB without a valid transaction context then a TransactionRequiredLocalException is thrown.

RhinoSbbLocalHome interface serviceActivating method

The SLEE invokes the serviceActivating method on the root SBB of a service when the service is about to transition from Inactive to Active. This method is invoked after the verifyConfiguration method has returned successfully and the SLEE has determined that the service activation can proceed to completion. The SBB can use this callback, for example, to initialise common state shared between SBB objects (such as the service context of the same SBB).

Child SBBs in the service may also receive this callback method invocation, as described in the Lifecycle callback method invocation cascade section below.

In Rhino, this method is invoked on a service on each cluster node where the service is about to transition to the Active state. The service transitions to the Active state on the corresponding node after this method returns.

If an SBB declares a local home interface, then a corresponding method with the following signature must be implemented in the SBB abstract class:

public void sbbHomeServiceActivating(Object config);

When this method is invoked by the SLEE, the object passed as an argument to this method is the return result from the previous corresponding verifyConfiguration method invocation.

This method is a mandatory transactional method. When this method is invoked by the SLEE, it is invoked with an active transaction context. If this method is invoked by an SBB without a valid transaction context then a TransactionRequiredLocalException is thrown.

RhinoSbbLocalHome interface serviceDeactivating method

The SLEE invokes the serviceDeactivating method on the root SBB of a service when no SBB entity trees remain in the service and it is about to transition from the Stopping state to the Inactive state. An SBB can use this callback, for example, to clean up any shared state that is no longer required after the service has deactivated.

Child SBBs in the service may also receive this callback method invocation, as described in the Lifecycle callback method invocation cascade section below.

In Rhino, this method is invoked on a service on each cluster node where the service is about to transition to the Inactive state. The service transitions to the Inactive state on the corresponding node after this method returns.

If an SBB declares a local home interface, then a corresponding method with the following signature must be implemented in the SBB abstract class:

public void sbbHomeServiceDeactivating();

This method is a mandatory transactional method. When this method is invoked by the SLEE, it is invoked with an active transaction context. If this method is invoked by an SBB without a valid transaction context, then a TransactionRequiredLocalException is thrown.

Lifecycle callback method invocation cascade

The SBB service lifecycle callback methods are initially invoked by the SLEE on the root SBB of a service. If the root SBB has child SBB relations where the cascade-service-lifecycle-callbacks option in the SBB extension deployment descriptor is set to True, then the SLEE will also automatically invoke the same lifecycle callback method on each of those child SBBs. This process repeats or "cascades" to each child SBB in the service where the cascade-service-lifecycle-callbacks option in the parent SBB requests it. The SLEE however will invoke this method at most once for each SBB type present in the service, regardless of how many times a given SBB appears as a child SBB in the service.

Lifecycle callback method invocation cascade is enabled by default on all child relations. Cascade may be disabled for a given child SBB relation by setting the corresponding cascade-service-lifecycle-callbacks option in the SBB extension deployment descriptor to False. As an alternative to automatic cascade, an SBB can manually invoke the lifecycle callback methods directly on any of its child SBBs by obtaining the child SBB’s local home object from the Child Relation Facility. Lifecycle callback methods invoked by an SBB, rather than the SLEE, do not cascade to other child SBBs, even if those child SBB relations are flagged for cascade.

Lifecycle callback method invocations proceed to cascade through eligible child SBB relations irrespective of whether or not the root SBB or any given child SBB declares a local home interface. An SBB that does not declare a local home interface is simply unaware that the callback method invocation occurred.

Per-Node Service Activity and Lifecycle Events

Tip Since Rhino 2.6.1

Service Node Activities can be used by SBBs to monitor the managed lifecycle of the service they are a part of on their individual cluster node. Service Node Activities perform the same function as SLEE 1.1 Service Activities, but for each node in a Rhino cluster instead of only once for the whole cluster. This is useful when a service needs to initialise local state on each node in a cluster or perform shutdown tasks without storing replicated SBB state.

When a service is started on a node, a Service Node Activity is created on that node and a com.opencloud.rhino.slee.servicenodeactivity.ServiceNodeStartedEvent is fired on the activity to the service. When the service is stopped on a node, the service node activity on that node is ended and a javax.slee.ActivityEndEvent is fired on the activity. A service starts on a node when either of the following occurs on that node:

  • the SLEE is in the Running state and the service is activated via the ServiceManagementMBean; or

  • the persistent state of the service says that the service should be active, and a previously stopped SLEE transitions to the Running state.

The event type name of Service Node Started events is com.opencloud.rhino.slee.servicenodeactivity.ServiceNodeStartedEvent, the vendor is com.opencloud, and the version is 1.0. The event, when fired by the SLEE, will only be delivered to SBBs in the service that is starting, and not any other service.

Service Node Activities are never replicated. Each node has their own Service Node Activity that does not share state with any other node.

The Service Node Activity allows the SBBs making up the service to identify the service. The Service Node Activity for an SBB entity can be obtained by looking it up using the ServiceNodeActivityFactory class, an instance of which can be obtained by JNDI lookup using the name java:comp/env/rhino/servicenodeactivity/factory. An SBB can create an Activity Context Interface for the Service Node Activity using the ServiceNodeActivityContextInterfaceFactory class. An instance of this can be looked up using the JNDI name java:comp/env/rhino/servicenodeactivity/activitycontextinterfacefactory.

Unchecked throwable propagation

The JAIN SLEE specification states that if a method invocation on an SBB or a profile returns by throwing an unchecked exception, then (amongst other things) the transaction is marked for rollback and a javax.slee.TransactionRolledBackLocalException is propagated back to the caller. There may be times, however, in certain applications where this behaviour is undesirable, and the caller would rather catch and handle the exception itself rather than have the transaction forcibly rolled back.

To address this need, Rhino provides the @PropagateUncheckedThrowables annotation. This annotation can be used on any SBB local interface method or profile local interface method to indicate that any unchecked throwable (RuntimeExceptions or Errors) produced by the method must be propagated back to the caller as-is. The transaction is not marked for rollback, and the invoked SBB or profile object remains in the same state; in other words, it is not discarded by a transition to the Does Not Exist state.

An SBB local interface or profile local interface class declaration may also be annotated with @PropagateUncheckedThrowables. When used in this way, the annotation indicates that all methods defined in the interface, and all inherited methods, shall exhibit the behaviour defined by the annotation, as if all these methods were annotated individually.

The propagation of unchecked throwables only applies to exceptions produced by the invoked method itself. The annotation has no effect against container-generated exceptions that cause rollback, such as trying to invoke a local interface method on an SBB that no longer exists. The annotation is also ignored for methods originally defined in the base local interfaces: javax.slee.SbbLocalObject and javax.slee.profile.ProfileLocalObject.

Convergence name session ownership record

Tip Since Rhino 2.6.1

A convergence name session ownership record is a session ownership record related to an SBB entity tree. These records are managed by Rhino using the session ownership store on behalf of each SBB entity tree. This means that applications do not need to create, update, or store the record directly themselves, they simply ask for the record and modify attributes on it in a CMP-style fashion as desired. Modifications to the underlying record will be stored automatically by Rhino during transaction commit, and the record will be automatically deleted when the SBB entity tree is removed.

The convergence name session ownership record for the current SBB entity tree can be obtained from the SBB context of an SBB, or SBB part context of an SBB part.

An application interacts with a convergence name session ownership record using the ConvergenceNameSessionOwnershipRecord interface. The ConvergenceNameSessionOwnershipRecord interface is shown below:

package com.opencloud.rhino.facilities.sessionownership;

import java.util.Map;
import com.opencloud.rhino.slee.ConvergenceName;

public interface ConvergenceNameSessionOwnershipRecord {
    public String getPrimaryKey();

    public ConvergenceName getConvergenceName();

    public Map<String,String> getAttributes();

    public String getAttribute(String name);

    public void setAttributes(Map<String,String> attributes, boolean exclusive)
        throws NullPointerException;

    public void setAttribute(String name, String value)
        throws NullPointerException;

    public boolean removeAttribute(String name);

    public long getTimeToLive();

    public void setTimeToLive(int ttl)
        throws IllegalArgumentException;
}

Note that this interface does not extend and is not related to the SessionOwnershipRecord interface used by the session ownership resource adaptor type. This is intentional to avoid a convergence name session ownership record — a Rhino-managed record — to be manipulated in the same way as a regular non-managed session ownership record. A convergence name session ownership record is intended to be manipulated by applications in a CMP-like manner, the various getter and setter methods on the interface serving to reinforce this mentality.

ConvergenceNameSessionOwnershipRecord interface getPrimaryKey method

The getPrimaryKey method returns the primary key of the session ownership record.

The main reason for exposing this key is to allow other protocol-specific session ownership records to link to this record as a master record of session ownership.

It is strongly discouraged that this key be used with the session ownership resource adaptor type to retrieve, update, or other manipulate the underlying session ownership record. Any manual update of the underlying record by an application will be overwritten by Rhino when it synchronises the managed record state with the underlying record state.

ConvergenceNameSessionOwnershipRecord interface getConvergenceName method

The getConvergenceName method returns a ConvergenceName object that describes the convergence name of the SBB entity tree associated with the record.

ConvergenceNameSessionOwnershipRecord interface get/set/removeAttribute methods

These methods get, set, or remove a single application-defined attribute in the record.

Only application-defined attributes may be manipulated by an application. While a convergence name session ownership record may define other attributes for internal use, these are not visible to application components.

ConvergenceNameSessionOwnershipRecord interface getAttributes method

The getAttributes method returns a map of attribute name to attribute value for all the application-defined attributes stored in the record.

ConvergenceNameSessionOwnershipRecord interface setAttributes method

The setAttributes method allows multiple application-defined attributes to be set in the record at once using a map of attribute names to attribute values.

If the exclusive method argument is true, then any existing application-defined attributes in the record are removed before the attributes specified in the map are set. In other words, after this method returns, the only application-defined attributes contained in the record will be those contained in the map passed as an argument to the method.

ConvergenceNameSessionOwnershipRecord interface get/setTimeToLive methods

All session ownership records have a time-to-live (TTL) period after which they expire and are deleted. The initial TTL of a convergence name session ownership record is set when the record is first obtained, but the TTL may be changed at any time by either:

  • reobtaining the record from the SBB context or SBB part context with the new TTL; or

  • invoking the setTimeToLive method on an already obtained ConvergenceNameSessionOwnershipRecord object with the new TTL.

The current record TTL, measured in milliseconds, can be obtained using the getTimeToLive method.

Convergence name session ownership record continuity

Rhino will use the TTL of a convergence name session ownership record to determine if and when a record needs refreshing to ensure its continued survival for the lifetime of the SBB entity. However, such checks are only made after an SBB entity tree processes an event. This means that an application should set the TTL of this record to a value greater than the maximum expected time between received events.

If a convergence name session ownership record expires, then all application-defined attributes will be lost.

Coordinated session adoption

Many SLEE applications use multiple types of activities within a single service instance (or SBB entity tree). For example there may be activities for call signaling, charging, and so on. The resource adaptors that manage each of these activities may maintain their own session ownership records in addition to the convergence name session ownership record managed by Rhino.

In a session failover scenario, there must be a coordinated approach to how a new node adopts a session such that race conditions do not occur. For example, if a signaling event is received on an activity by one surviving node at the same time a charging event for the same session is received on another related activity on another node, there must be a mechanism for resolution such that the session only gets adopted by one of the nodes, otherwise application state becomes fragmented and inconsistent.

The purpose of the convergence name session ownership record is to allow the protocol sessions (and their internal state variables), as well as the application state (SBB entity CMP variables) to move between SLEE nodes together, to preserve sticky-sessions. During failover, adoption of a session is controlled by a race for ownership, and the convergence name session ownership record is the single point of contact for that race.

The mechanism as a whole is intended to work as follows:

  • Each resource adaptor that supports failover creates and manages a session ownership record for each activity or set of related activities. A protocol-specific mapping is used to obtain a session ownership record primary key from an activity handle or identifier.

  • These resource adaptors also provide an API that allows an application to indicate the protocol-specific session ownership record should be linked with its convergence name session ownership record. For example, the resource adaptor could provide a method that takes an activity object and a convergence name session ownership record primary key as arguments.

  • The resource adaptor then links its protocol-specific session ownership record with the convergence name session ownership record. It does this by setting an attribute on the protocol-specific record with the value of the convergence name record primary key passed in by the application. It is recommended that resource adaptors use the SessionOwnershipRecord.ASSOCIATED_RECORD_ATTRIBUTE_NAME attribute for this purpose but this is not mandatory.

  • When the resource adaptor receives an event on an activity for which it has no local state, it retrieves the corresponding protocol-specific session ownership record and extracts the primary key of the convergence name session ownership record from that. The resource adaptor then uses the SessionOwnershipFacility.tryAdoptRecord method, passing in the convergence name record primary key, to try to claim ownership of the convergence name session ownership record.

    • If the adoption attempt results in the application session being owned by the current node, then the resource adaptor restores any activity session state that it needs to and continues to process the event locally.

    • If not, the adoption result may indicate that the event should be forwarded to another cluster node, or that the adoption attempt was inconclusive and may need to retried in a short period of time. A resource adaptor should limit the maximum number of times it will retry an adoption attempt before giving up and aborting the session.

Session adoption pseudo-code

The pseudo-code shown below illustrates how a resource adaptor is expected to use the session ownership facility to adopt a session. Although the session ownership facility only provides asynchronous operations for record retrieval, this pseudo-code is written assuming synchronous operations for the purposes of brevity and clarity.

SessionOwnershipFacility sessionOwnershipFacility = ...;

// this method is called when a network event has been parsed
// but no other "receive logic" has executed yet
void onRequest(Request request, SessionID sessionID) {
    if (haveLocalState(sessionID)) {
        // no need to care about session ownership because the session state is already here
        processRequestHere(request, sessionID);
        return;
    }

    // note: the real retrieveRecord() operation here is asynchronous
    SessionOwnershipRecord protoSpecificRecord = sessionOwnershipFacility.retrieveRecord(sessionID.toTrackingKey());
    if (protoSpecificRecord == null || protoSpecificRecord.getAttribute(SessionOwnershipRecord.ASSOCIATED_RECORD_ATTRIBUTE_NAME) {
        log.debug("do not have a protocol specific record, or don't have an associated-record attribute");
        sendTemporaryErrorResponse(request, sessionID);
        return;
    }

    boolean success = tryAdopt(request, sessionID, protoSpecificRecord);
    if (!success) {
        log.debug("adoption loop gave up");
        sendTemporaryErrorResponse(request, sessionID);
        return;
    }

    log.debug("successfully handled request");
}

// this method attempts to adopt the session
// it returns true if successful, false if we gave up
boolean tryAdopt(Request request, SessionID sessionID, SessionOwnershipRecord protoSpecificRecord) {
    String convergenceNamePKey = protoSpecificRecord.getAttribute(SessionOwnershipRecord.ASSOCIATED_RECORD_ATTRIBUTE_NAME);
    boolean finishedAdopt = false;
    int adoptCount = 0;

    // other nodes may have received a request for a related record too, so we need to race
    SessionOwnershipAdoptionResult adoptRes;
    while (!finishedAdopt && adoptCount <= 3) {
        adoptRes = sessionOwnershipFacility.tryAdoptRecord(convergenceNamePKey);
        log.debug("tryAdoptRecord(%s) result is %s", convergenceNamePKey, adoptRes);

        switch (adoptRes.getType()) {
            case ALREADY_OWNED_BY_THIS_NODE:
                log.debug("I am already owner for session %s", sessionID);
                finishedAdopt = true;
                processRequestHere(request, sessionID);
                return true;
            case ALREADY_OWNED_BY_OTHER_NODE:
                log.debug("Another node %s is already owner for session %s", adoptRes.getNodeID(), sessionID);
                finishedAdopt = true;
                break;
            case RACE_WON_BY_THIS_NODE:
                // we won the race - process it here
                // to process it here we have to
                // 1) adjust the protocol specific record to say this node is the owner
                //    so that future reads of that record note the correct owner for the record
                // 2) process the request here
                log.debug("I won the race for session %s", adoptRes);
                finishedAdopt = true;
                String previousOwner = findLocalOwnerURI(protoSpecificRecord.getOwnerUris());
                String newOwner = computeOwnerURI(request);
                SessionOwnershipRecord newProtoSpecificRecord = protoSpecificRecord
                    .toBuilder()
                    .removeOwnerURI(previousOwner)
                    .addOwnerURI(newOwner)
                    .build();
                // we don't need to wait for the result of this operation
                // if it fails there's nothing we can do and if it succeeds we don't need the latency
                sessionOwnershipFacility.storeRecord(newProtoSpecificRecord, myListener);
                processRequestHere(request, sessionID);
                return true;
            case RACE_WON_BY_OTHER_NODE:
                // another node won the race, back off and start the read again
                break;
            case RECORD_VIEW_ID_NEWER:
                // current node is behind a more recent cluster view change
                // back off and try the read again
                break;
            case RECORD_NOT_APPROPRIATE:
                // the convergence name pkey did not identify an appropriate convergence name record for the race
                break;
            case RECORD_DOES_NOT_EXIST:
                // the convergence name record was identifed but does not exist in the session ownership store
                break;
            case RECORD_NOT_SAME_CLUSTER:
                // the related record is not in the same cluster
                break;
            case SYSTEM_ISSUE:
                // an internal error occurred, back off and retry
                break;
        }
        if (!finishedAdopt) {
            Thread.currentThread().sleep(INTER_CAS_SLEEP);
        }
        adoptCount++;
    }

    if (!finishedAdopt) {
      // we've retried several times and it didn't work, give up
       return false;
    }

    // now we know the adoption result
    // if it was won by this node, or already owned by this node, we'd have processed and returned
    // now we have to wait for the protocol specific record to catch up
    // first use the record we've already got and see if in sync
    if (protocolRecordAndAdoptionResultInSync(protoSpecificRecord, adoptRes)) {
        // they are in sync, proxy and return
        moveRequestSideways(request, protoSpecificRecord);
        return true;
    }

    // the first protocol specific record was not in sync, so loop on that waiting for it to be updated
    boolean inSyncProtocolRecord = false;
    int readCount = 0;
    while (!inSyncProtocolRecord && readCount <= 3) {
        // note: the real retrieveRecord() operation here is asynchronous
        SessionOwnershipRecord protoSpecificRecord = sessionOwnershipFacility.retrieveRecord(sessionID.toTrackingKey());
        if (protoSpecificRecord == null || protoSpecificRecord.getAttribute(SessionOwnershipRecord.ASSOCIATED_RECORD_ATTRIBUTE_NAME) {
            log.debug("do not have a protocol specific record, or don't have an associated-record attribute");
            return false;
        }
        if (protocolRecordAndAdoptionResultInSync(protoSpecificRecord, adoptRes)) {
            moveRequestSideways(request, protoSpecificRecord);
            return true;
        }
        else {
            Thread.currentThread().sleep(INTER_PROTO_READ_TIMEOUT);
        }
        readCount++;
    }
    // give up
    return false;
}

void moveRequestSideways(Request request, ProtocolURI destination) {
   // do something protocol and resource adaptor specific
   // to move the request to another node
}

void processRequestHere(Request request, SessionID sessionID) {
   // go to higher layers in the receiving protocol stack
   // eventually calling SLEEEndpoint.fireEvent
}

String findLocalOwnerURI(Set<String> ownerURIs) {
    // returns the owner URI from the set that looks like an owner URI formatted by this resource adaptor
}

String computeOwnerURI(Request request) {
    // returns a string that identifies this node as the owner of the request
}

boolean protocolRecordAndAdoptionResultInSync(SessionOwnershipRecord record, AdoptionResult result) {
    // if the record's owner URI's encoded cluster ID and node ID match the AdoptionResult
    // then return true - they are in sync
}
Previous page