The new version of CoherentSolutions.Extensions.Hosting.ServiceFabric is released!
The version 1.2.1 is available on NuGet.
The release notes can be found on project version page.
What’s new?
This release was fully dedicates to allow configuring delegates to react on service events. This was the major step in the support of service life-cycle and it will be used as a shoulder for future improvements.
Let’s see the details.
Defining a delegate
In understanding of CoherentSolutions.Extensions.Hosting.ServiceFabric the delegate represents an action to be invoked in a reaction to service event. So previously when we were defining a delegate like this:
new HostBuilder() .DefineStatefulService( serviceBuilder => { serviceBuilder.DefineDelegate( delegateBuilder => { ... }); }) .Build() .Run()
… we were actually configuring this delegate to be invoked on services OnRun event.
The OnRun
service event is triggered when stateful service primary replica invoked RunAsync method or when stateless service instance invoked RunAsync method.
Note!
This behavior is still there in terms of backward compatibility.
This looks nice but what are the events we actually can react to?
This depends on the type of the service. Both stateful service and stateless service have different life-cycles (I won’t describe them as part of this post but I am planning to publish a huge post about Service Fabric – Service Fabric Handbook that you will be able to use as reference with all this staff covered) and therefore they have different set of events to react on.
Events
Service event is triggered as part of service life-cycle event. The purpose of these events is to execute code under certain conditions (life-cycle event) with specific guarantees (before or after something, with or without access rights etc.).
Let’s drilldown all the events one by one.
Stateful Service
Stateful Service has very sophisticated life-cycle because each replica can be in more that one role (primary, idle secondary, active secondary) without being recreated i.e. can change roles dynamically. This results in different access rights to reliable state and multiple event triggers.
In the table below there is a summary of all important information about stateful service events:
Name | Description | Write Access* | Read Access |
---|---|---|---|
OnStartup |
This event is triggered when replica is initialized. This event doesn’t triggered during promotion or demotion cycles.
All delegates are guaranteed to be executed before any of ICommunicationListeners returned by CreateServiceReplicaListeners are opened.
|
No | No |
OnChangeRole | This event is triggered each time replica role is changed (event can be triggered multiple times in order to change replica role). The replica can change roles by multiple reasons: startup, shutdown, promotion, demotion etc. | No | No |
OnRun |
This event is triggered only on primary replica.
All delegates are guaranteed to be executed after all
ICommunicationListeners are opened and replica role is changed to ReplicaRole.Primary.
|
Yes | Yes |
OnShutdown |
This event is triggered when replica is being shutdown. The replica should release all resources and perform the cleanup.
All delegates are guaranteed to be executed after all
ICommunicationListeners are closed.
|
No | Yes |
OnDataLoss |
This event is triggered when replica detected data loss.
The event mirrors the invocation of OnDataLossAsync method of the
StatefulServiceBase class.
|
No | No |
OnRestoreCompleted |
This event is triggered when replica state is restored.
The event mirrors the invocation of OnRestoreCompletedAsync method of the
StatefulServiceBase class.
|
No | No |
* The write access is applicable only if replica is primary replica on the moment of event.
OnChangeRole
and OnShutdown
events have a payload objects. These objects provides extra information about the event.
The OnChangeRole event payload allows to identify the role replica is being changed to:
public interface IStatefulServiceEventPayloadOnChangeRole { ReplicaRole NewRole { get; } }
The OnShutdown
event payload allows to identify whether replica shutdown is caused by common replica termination i.e. replica movements or it is an unplanned situation that caused Service Fabric to interrupt replica.
public interface IStatefulServiceEventPayloadOnShutdown { bool IsAborting { get; } }
The OnDataLoss
event payload allows yo handle the replica status restoration process by providing a thin wrapper around RestoreContext:
public interface IStatefulServiceEventPayloadOnDataLoss { IStatefulServiceRestoreContext RestoreContext { get; } } public interface IStatefulServiceRestoreContext { Task RestoreAsync( RestoreDescription restoreDescription); Task RestoreAsync( RestoreDescription restoreDescription, CancellationToken cancellationToken); }
Stateless Service
Stateless Service life-cycle is much simpler than life-cycle of stateful service.
In the table below there is a summary of all important information about stateless service events:
Name | Description |
---|---|
OnStartup |
This event is triggered when new instance is initialized.
All delegates are guaranteed to be executed before any of
ICommunicationListeners returned by CreateServiceInstanceListeners are opened.
|
OnRun |
This event is triggered when instance is started.
All delegates are guaranteed to be executed after all
ICommunicationListeners are opened.
|
OnShutdown |
This event is triggered when instance is being shutdown. The instance should release all resources and perform the cleanup.
All delegates are guaranteed to be executed after all
ICommunicationListeners are closed.
|
The OnShutdown
event payload allows to identify whether instance shutdown is caused by common instance termination i.e. instance movements or it is an unplanned situation that caused Service Fabric to interrupt instance.
public interface IStatelessServiceEventPayloadOnShutdown { bool IsAborting { get; } }
Now when we know what kind of events we can use let’s see how we can configure the delegate to react on service event.
Using events
Configuration of delegate to react on event is done by calling UseEvent
method:
new HostBuilder() .DefineStatefulService( serviceBuilder => { serviceBuilder.DefineDelegate( delegateBuilder => { delegateBuilder .UseEvent(...) .UseDelegate(...); }); }) .Build() .Run()
Depending on the type of the service (stateful or stateless) the UseEvent
accepts either StatefulServiceLifecycleEvent or StatelessServiceLifecycleEvent.
The same delegate can be configured to react on multiple events by passing a combination of events in UseEvent
method:
new HostBuilder() .DefineStatefulService( serviceBuilder => { serviceBuilder.DefineDelegate( delegateBuilder => { delegateBuilder .UseEvent( StatefulServiceLifecycleEvent.OnStartup | StatefulServiceLifecycleEvent.OnShutdown) .UseDelegate(...); }); }) .Build() .Run()
The delegate action (the one configured in UseDelegate
) can receive delegate invocation context (IStatefulServiceDelegateInvocationContext in case of stateful service or IStatelessServiceDelegateInvocationContext in case of stateless service.):
new HostBuilder() .DefineStatefulService( serviceBuilder => { serviceBuilder.DefineDelegate( delegateBuilder => { delegateBuilder .UseEvent( StatefulServiceLifecycleEvent.OnStartup | StatefulServiceLifecycleEvent.OnShutdown) .UseDelegate( IStatefulServiceDelegateInvocationContext invocationContext => { ... }); }); }) .Build() .Run()
The invocation context can be used to find out the type of service event delegate is invoked on. If the service event has payload then invocation context can be casted to specific invocation context type:
new HostBuilder() .DefineStatefulService( serviceBuilder => { serviceBuilder.DefineDelegate( delegateBuilder => { delegateBuilder .UseEvent( StatefulServiceLifecycleEvent.OnStartup | StatefulServiceLifecycleEvent.OnShutdown) .UseDelegate( IStatefulServiceDelegateInvocationContext invocationContext => { switch (invocationContext.Event) { StatefulServiceLifecycleEvent.OnShutdown: var context = (IStatefulServiceDelegateInvocationContextOnShutdown)invocationContext; ... break; } }); }); }) .Build() .Run()
When service event type is known is advance (the delegate is attached to single event) then event payload object can be injected directly:
new HostBuilder() .DefineStatefulService( serviceBuilder => { serviceBuilder.DefineDelegate( delegateBuilder => { delegateBuilder .UseEvent(StatefulServiceLifecycleEvent.OnShutdown) .UseDelegate( (IStatefulServiceEventPayloadOnShutdown payload) => { ... }); }); }) .Build() .Run()
While this isn’t something new but it still worth mentioning – the action passed in UseDelegate
method can be asynchronous. This is achieved by passing a Func<...,Task>
as value to UseDelegate
method:
new HostBuilder() .DefineStatefulService( serviceBuilder => { serviceBuilder.DefineDelegate( delegateBuilder => { delegateBuilder .UseEvent(StatefulServiceLifecycleEvent.OnShutdown) .UseDelegate( async () => { await Task.Delay(100); }); }); }) .Build() .Run()
Conclusion
The new release of CoherentSolutions.Extensions.Hosting.ServiceFabric now provides a simple way to react on different service life-cycle event. Currently each event provides a base minimum of the information but this is the subject to change and expand in future.
Thank you for reading!
Acknowledgement
CoherentSolutions.Extensions.Hosting.ServiceFabric is an open source project owned and maintained by Coherent Solutions Inc.