Handling Expiration Events

The ScaleOut StateServer service can notify your client application code when an object is expiring. Object expirations can occur for one of three reasons:

  • An object is about to be removed from the ScaleOut service because its timeout has expired.
  • A preemptable object is being removed from ScaleOut service due to a low memory condition.
  • An object is being removed due to a dependency relationship.

The sosscli::SossEvents class allows your application to register callbacks that will be invoked when one of these events is raised by the ScaleOut StateServer service:

#include "soss_client/named_protobuf_cache.h"
#include "soss_client/soss_events.h"
#include "StockQuote.pb.h"
#include "boost/thread.hpp"

using namespace NativeClientSample;

// Function that receives callbacks for expirations events:
sosscli::SossEvents::ObjectDisposition callback(sosscli::SossEvents::EventDetails const & details)
{
  // Print the reason for the expiration:
  switch (details.event_code())
  {
  case sosscli::SossEvents::ObjectTimeout:
    std::cout << "Object timed out." << std::endl; break;
  case sosscli::SossEvents::LowMemory:
    std::cout << "Low memory eviction." << std::endl; break;
  case sosscli::SossEvents::Dependency:
    std::cout << "Dependency relationship." << std::endl; break;
  default:
    std::cout << "Unexpected expiration reason." << std::endl; break;
  }

  // Tell the server what to do with the expiring object (allow it to be removed or save it):
  return sosscli::SossEvents::Remove; // could also be SossEvents::Save or NotHandled
}

int main(int argc, char* argv[])
{
  sosscli::NamedProtobufCache<StockQuote> quote_cache("Stock Quotes");

  // Register callback to handle expirations for our quote_cache:
  sosscli::SossEvents::register_callback(quote_cache, callback);

  // Create an object and insert it into the StateServer service with a 10-second timeout:
  auto quote = boost::make_shared<StockQuote>();
  quote->set_ticker("GOOG");
  quote->set_price(1054.48f);
  quote->set_volume(1373206);

  sosscli::ObjectPolicy policy;
  policy.set_timeout(boost::posix_time::seconds(10));
  policy.set_timeout_type(sosscli::ObjectPolicy::Absolute);

  quote_cache.insert("GOOG", quote, policy);

  // Sleep to give the object enough time to expire so the callback can print its message.
  boost::this_thread::sleep(boost::posix_time::seconds(15));
  return 0;
}

Multiple callback methods may be registered for a named cache by calling the SossEvents::register_callback() method repeatedly. Callback functions will be called one at a time in the order they were registered until a callback indicates that it has "handled" the event by returning a result other than NotHandled.

The SossEvent class' static register_callback method takes a boost::function object as a parameter. This callback must accept a constant reference to a sosscli::SossEvents::EventDetails object and return a sosscli::SossEvents::ObjectDisposition enum value.

The EventDetails callback parameter provides information about which object is expiring (via the EventDetails::key() accessor) and why it’s expiring (via the EventDetails::event_code() accessor). This information can be used in your callback to take appropriate action.

The ObjectDisposition enum value that is returned from your callback is used to tell the server what to do with the expiring object. Three values are available:

Save
The callback would like to keep the object in the store.
Remove
The callback would like the object removed from the store.
NotHandled
The callback has not handled the event; if there are additional callbacks they will be given the opportunity to handle the event. If not, the default action (Remove) will be taken.

Since a boost::function object is used by the API to store your callback, you are not restricted to using simple function as your callback—functors and class member functions can also be used. Binding libraries like boost::bind can be used for increased flexibility when registering a callback.

For example, we can create a callback functor that examines an expiring object prior to its removal:

// Functor that receives callbacks for expiration events.
struct ExpirationCallback
{
  // This function object will hold its own copy of a named cache:
  sosscli::NamedProtobufCache<StockQuote> nc_;

  // Constructor
  ExpirationCallback(sosscli::NamedProtobufCache<StockQuote> nc) : nc_(nc) {}

  // operator() definition
  sosscli::SossEvents::ObjectDisposition operator()(sosscli::SossEvents::EventDetails const & details)
  {
    // Retrieve the expiring object:
    auto get_res = nc_.get(details.key());
    std::string ticker_symbol = get_res.object_ptr()->ticker();

    std::cout << ticker_symbol << " is expiring." << std::endl;
    return sosscli::SossEvents::Remove;
  }
};

Registration of this callback would simply involve instantiating the function object prior to calling register_callback:

sosscli::NamedProtobufCache<StockQuote> quote_cache("Stock Quotes");
ExpirationCallback functor_callback(quote_cache);
sosscli::SossEvents::register_callback(quote_cache, functor_callback);
[Note] Note

In a server farm that is running multiple ScaleOut hosts, it is not possible to predict which host will receive an expiration event for any given object. This allows ScaleOut StateServer to maximize performance by load balancing event delivery across the server farm. It is important, therefore, to have the event-handling code running on each ScaleOut server to ensure that the expiration event is not lost. If the server farm is accessed solely by remote clients, there must be at least as many active remote clients as there are ScaleOut hosts; this ensures that all ScaleOut hosts can deliver events to a client. This requirement applies to all applications that perform event handling.