Table of Contents

Expiration Events

When an object expires from a cache, the ServiceEvents.SetExpirationHandler method can be used to specify custom expiration logic that is invoked just before the object is removed from the cache.

Objects expire from the ScaleOut service for one of three reasons:

  • An object's timeout expired.
  • An object is being removed due to a dependency relationship.
  • Low memory in the ScaleOut service, triggering expiration of the least recently accessed object(s).

Expiration notifications are useful for many use cases, including:

  • Persisting an object to a database after it times out due to inactivity.
  • Refreshing or re-creating a removed object immediately after it expires.
  • Logging or debugging to better understand changes to a cache's population.

Registering an Expiration Callback

The following example illustrates how to register an expiration callback using C# lambda syntax with the ServiceEvents.SetExpirationHandler method:

using System;
using System.Threading.Tasks;
using Scaleout.Client;

class Expiration
{
    static async Task Main(string[] args)
    {
        var conn = await GridConnection.ConnectAsync("bootstrapGateways=localhost:721");

        // Set up a cache with a 60-second object expiration policy:
        var cache = new CacheBuilder<int, string>("presidents", conn)
                        .SetObjectTimeout(TimeSpan.FromSeconds(60), TimeoutType.Sliding)
                        .Build();

        // Inform the ScaleOut service that this client will be processing expiration
        // events by providing a lambda callback for the presidents cache:
        ServiceEvents.SetExpirationHandler(cache, 
                                           async (expiringKey, expirationType) =>
        {
            // Retrieve the object that is about to expire:
            var response = await cache.ReadAsync(expiringKey);

            Console.WriteLine($"President {response.Value} has timed out.");

            // The returned disposition value allows you to choose between 
            // removing the object or allowing it to remain:
            return ObjectDisposition.Remove;
        });

        // Add an object:
        await cache.AddAsync(9, "William Henry Harrison");

        // Wait for the object to expire so we can see the expiration
        // handler get fired:
        await Task.Delay(TimeSpan.FromSeconds(70));
    }
}

Two arguments are supplied to your expiration callback: the key to the expiring object (expiringKey) and the reason that the object is expiring (expirationType), an ExpirationType enum. The reason for the expiration in the example above is that the cached object expires after 60 seconds, so expirationType is set to Timeout when the callback is triggered.

The value returned from your callback must be an ObjectDisposition enum value that indicates to the ScaleOut service whether the expiring object should be removed or whether the expiration should be prevented.

Note

All event registration methods exposed by the ServiceEvents class are overloaded to accept either synchronous or async callback methods/lambdas.

Important

Expiration callbacks may be invoked in parallel in your client application. Any shared state that is accessed from the callback should be protected by an appropriate synchronization mechanism.

Service Configuration

The event-handling feature must be enabled in the ScaleOut service for expiration events to fire. This feature is enabled by default -- the max_event_tries parameter in the service's soss_params.txt file controls eventing in the ScaleOut service. See the Configuration Parameters topic in the ScaleOut Software User Guide for details on the soss_params.txt file.

Note that in a server farm that is running multiple ScaleOut hosts, it is not possible to predict which host will receive an expiration event. This allows ScaleOut StateServer to maximize performance by load balancing event delivery across instances of your client application. It is important 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.

Deployment Considerations

To improve the reliability of your event handling logic, it is recommended that you handle expiration events in a dedicated process that runs locally on each host StateServer host (for example, in a Windows Service process or a Linux daemon). Doing so offers several advantages over handling events in the main client application (which is often hosted in a web server's worker process):

  • Improved performance by reducing network usage: The ScaleOut service automatically routes events to the local event handling service, improving responsiveness and reducing network usage.
  • Predictable process lifetime: The processes hosting a web app are often designed to be ephemeral. For example, IIS on Windows might decide to stop a .NET worker process because it has been idle for too long, causing missed events if another instance of your application isn't available to take over event handling duties.
  • It protects your web app: Generally, you only want your web application process to be concerned with handling web requests. Event handling logic may be resource-intensive and affect the responsiveness of your application.
  • It matches the availability/scalability model of hosts in the SOSS cluster: the count/lifetime of event handling services is the same as the count/lifetime of SOSS hosts. You don't need to be concerned about having enough instances of your main client application running to handle events.