A Second Look at Project Orleans

Project Orleans is a preview from Microsoft Research of an Actor-based framework and runtime supporting the development and deployment of massively distributed systems hosted in Microsoft Azure. A specific goal of Orleans is to simplify the creation of distributed systems for developers who are not skilled in the art.

Orleans supports the core features of the Actor model: state encapsulation and safe messaging; fair scheduling; location transparency; and mobility. An Orleans grain (actor) contains fully encapsulated state that may only be changed by the grain itself, in response to a message it receives. Deep copy is used whenever data is inserted into a message. Instead of the default pre-emptive multi-threading .NET scheduler, Orleans uses a cooperative multi-threading scheduler to schedule the processing of messages by a grain and ensures that a message to a grain is completely processed before the next message is processed. Orleans manages the activation of a grain in a silo on a physical node and provides location transparency by completely hiding grain location from the application. Grains are virtual and may or may not be activated in a silo when they are not being used. This allows the Orleans runtime to support weak mobility since at different times the same grain may be activated in different silos.

This is a follow-up to an earlier post which gave a high-level overview of Orleans as well as providing a variety of links to the Orleans system downloads and documentation.

Grains

An Orleans application comprises a system of interacting grains of various types. The application is developed by defining a set of grain interfaces which are then implemented in a set of classes. The Orleans build system auto-generates an associated set of factory and reference classes. The application is deployed through deploying the assemblies hosting the grain implementations to the physical nodes hosting the silos and deploying the assemblies hosting the factory and reference class implementations to the clients, which may or may not be hosting silos. The Orleans runtime completely manages access to grains and clients only ever access grain references, regardless of whether or not the client is hosted in an Orleans silo (i.e., is another grain).

Grain Interfaces

The Orleans API exposes an IAddressable interface, the base for a number of marker interfaces used in the definition of grain classes. In essence, the IAddressable interface indicates the addressability through the Orleans runtime of objects implementing the marker interfaces.

The interface hierarchy for IAddressable is:

IAddressable
   IGrain
   IRemindable
   IGrainObserver

These are declared as follows:

public interface IAddressable {}

public interface IGrain : IAddressable {}

public interface IGrainObserver : IAddressable {}

public interface IRemindable : IGrain, IAddressable {
   Task ReceiveReminder(String reminderName, TickStatus status);
}

IAddressable is an empty marker interface indicating that the Orleans runtime is able to address an instance implementing one of the derived interfaces. IGrain is an empty marker interface indicating that any derived interface is a grain interface. IGrainObserver is an empty marker interface indicating that a derived interface is implemented by an observing class. IRemindable is a marker interface indicating that an implementing class can receive reminders.

IGrain is a core interface for Orleans. Every grain class implements an interface derived from IGrain. These grain interfaces specify the functionality of the grains used in an Orleans application. The messages sent to grains are implemented as public methods and properties in the grain classes. Since Orleans is a distributed systems it is crucial that message processing is asynchronous and this is achieved by constraining grain interfaces so that all methods and properties return either a Task or a Task<T>. The async/await feature of .NET 4.5 greatly simplifies this. Methods can be defined as usual in the grain interface. However, properties must be handled in a special manner since the set method for a property essentially returns a void instead of a Task or Task<T>. Instead, a set method must be provided explicitly.

IGrainObserver is a marker interface indicating that an implementing class is able to observe a grain and process notifications issued by the grain. An observer implements an interface derived from IGrainObserver, the method of which are constrained to return only void. This means that instances of this class are not normal grains, for which the methods can return only Task or Task<T>. An observer must indicate to a grain that it must be notified of particular event, so the observing grain must expose methods supporting that subscription. A grain class can manage these subscriptions using the ObserverSubscriptionManager<T> class, with T being the observer interface. This class is declared as follows:

public class ObserverSubscriptionManager<T> where T : IGrainObserver {
   public ObserverSubscriptionManager<T>();
   public Int32 Count { get; }
   public void Clear();
   public void Notify(Action<T> notification);
   public void Subscribe(T observer);
   public void Unsubscribe(T observer);
}

Subscribe() adds an observer to the list of subscribers to be notified for the specific observable event. Unsubscribe() removes a specific observer from the notification list, while Clear() removes all subscribers. The notification is performed by invoking Notify() and invoking the appropriate notification action on each observing subscriber.

Grain Classes

The hierarchy for the classes implementing IAddressable is:

GrainBase
  GrainBase<TGrainState>
GrainReference

The implementation of a grain is provided by a class derived from either GrainBase or GrainBase<T> and which implements the appropriate grain interface class. GrainBase<T> extends the core GrainBase functionality by adding support for the persistence of grain state of type T, where T is an implementation of IGrainState.

GrainBase is declared as follows:

public abstract class GrainBase : IAddressable {
   protected GrainBase();
   public String IdentityString { get; }
   public String RuntimeIdentity { get; }
   public virtual Task ActivateAsync();
   public virtual Task DeactivateAsync();
   protected void DeactivateOnIdle();
   protected void DelayDeactivation(TimeSpan timeSpan);
   protected OrleansLogger GetLogger(String loggerName);
   protected OrleansLogger GetLogger();
   protected Task GetReminder(void reminderName);
   protected Task GetReminders();
   protected IStreamProvider GetStreamProvider(String name);
   protected IEnumerable<IStreamProvider> GetStreamProviders();
   protected Task RegisterOrUpdateReminder(void reminderName,
      IOrleansReminder dueTime, String period);
   protected IOrleansTimer RegisterTimer(Func asyncCallback,
      Boolean state, Object dueTime, Task period);
   protected Task UnregisterReminder(IOrleansReminder reminder);
}

IdentityString opaquely identifies the grain and RuntimeIdentity opaquely identifies the silo hosting it. ActivateAsync() is invoked each time the grain is activated – i.e., rehydrated into memory – and may be overridden to provide any additional initialization required. Similary, DeactivateAsync() is invoked each time the grain is deactivated and may be overridden (e.g., to persist grain state). DeactivateAsync() indicates that the grain should be deactivated as soon as the current request has completed. DelayDeactivation() hints that the grain should remain activated for the specified timespan. GetLogger() gets the Orleans logger which can be used to write entries to the Orleans log. In Azure, this log is persisted into the standard Azure logs provided by (Windows) Azure Diagnostics so may be accessed in the WADLogsTable in Azure Storage.

Orleans provides a timer capability, in which a timer can be created and associated with a grain. When this timer fires a method is invoked on the grain. It may be used, for example, to ensure that grain state is persisted periodically. This timer exists only while the grain is activated, and is cancelled whenever the grain is deactivated. The RegisterTimer() method is used to create a timer and specify the Task to be invoked when it fires. A timer is cancelled by disposing the handle returned by the RegisterTimer() method.

The Orleans reminder feature provides the capability of a timer which transcends grain lifetime. It does this by storing the reminder state either in-memory on the Silo (useful for development) or in an Azure Table. The latter is a distributed, persistent store, and its use allows a reminder to be sent even when a grain has been activated in another silo. The RegisterOrUpdateReminder() method is used to create or update a reminder, which is subsequently identified by name. For a grain to receive a reminder its class must implement the IRemindable interface. This interface exposes a ReceiveReminder() method which is invoked when the reminder is sent. The GetReminders() method returns all the reminders for the grain while the GetReminder() returns a reminder by name. UnregisterReminder() is used to delete a reminder.

The Orleans team has indicated that GetStreamProvider() and GetStreamProviders() should not have been exposed in the preview.

Persistent Grains

Orleans supports the persistence of grain state through the use of the GrainBase<TGrainState> class, where TGrainState is the class containing the data to be persisted. The grain state is persisted using a storage provider configured in the Orleans configuration file. When a grain is activated it is automatically initialized with its persistent state prior to the invocation of ActivateAsyn(), which can then be used to complete initialization (e.g., initialize any non-persisted state). However, the grain state is never persisted automatically so some strategy must be devised for grain state persistence.

In many Orleans systems the true grain state is actually resident on a client (for example, on an XBox controller) so can always be refreshed from there. Consequently, it is not necessarily crucial that the grain state be persisted whenever it is changed on the grain. It can be persisted occasionally using a timer or reminder and using DeactivateAsync() when the grain is deactivated. This deferred persistence writing helps improve performance.

Orleans provides in-memory and Azure Table storage providers – the former being suitable for development while the latter supports a production system. Orleans provides an extension point for creating storage providers. The Orleans team has provided a sample on CodePlex and Richard Astbury has created a GitHub repo with an Azure Blob storage provider.

GrainBase<T> is declared as follows:

public class GrainBase<TGrainState> :
      GrainBase, IAddressable where TGrainState : IGrainState {
   public GrainBase<TGrainState>();
   protected TGrainState State { get; }
}

The persistent state is accessed through the State property, which is strongly typed allowing its members to be accessed using property dot notation (e.g., State.LastName). A class derived from GrainBase<T> can have state not contained in State, but this is not persisted using the state persistence capability. This additional state can be managed in various ways including through the use of the ActivateAsyn() and DeactivateAsync() methods on GrainBase.

The TGrainState interface is derived from the IGrainState interface, which is declared as follows:

public interface IGrainState {
   String Etag { get; set; }
   Dictionary<String,Object> AsDictionary();
   Task ClearStateAsync();
   Task ReadStateAsync();
   void SetAll(Dictionary<String,Object> values);
   Task WriteStateAsync();
}

ClearStateAsync() is used to clear the current state. ReadStateAsync() is used to refresh the State property from the configured storage provider. WriteStateAsync() is used to persist the State property to the configured storage provider. AsDictionary() is used by state providers to expose the state as a Dictionary. SetAll() is used by storage providers to initialize the State property. ETag is an opaque value used by the storage provider.

Client Implementation

The Orleans server implementation comprises the grain interface and the grain class. The Orleans build system auto-generates a factory class and a reference class for each grain interface – and these are used by clients regardless of whether or not they are hosted in an Orleans silo. The factory class exposes static methods for creating grain references. The reference class implements the grain interface and the Orleans runtime proxies method invocations as messages to the actual grain.

The factory class implements methods like the following (where ISampleGrain is the grain interface):

public static ISampleGrain GetGrain(Guid primaryKey)
public static ISampleGrain GetGrain(long primaryKey)

These are used by clients to create grain references for the grain identified by the specified primary key. Note that this is purely a local operation and does not in itself cause the activation of a grain; that requires the invocation of a grain method.

The grain reference nominally has the type of the grain interface. It is actually an implementation of an auto-generated class derived from GrainReference and which implements the grain interface.

Example – Grain Interface

The following is a simple example of a grain interface:

public interface IPersonGrain : IGrain {
   Task<String> Name { get; }
   Task SetName(String name);
}

This example shows the use of a property getter with a standard method used instead of a property setter. As required all methods in the interface return either Task or Task<T>.

Example – State Interface

The following is a simple example of a state interface that persists only a single property:

public interface IPersonState : IGrainState {
   String Name { get; set; }
}

Example – Grain Class

The following is a simple example of a grain class implementing IPersonGrain and using the built-in grain persistence. Orleans loads state automatically on grain activation but grain state must be explicitly performed – in this case when the grain is deactivated.

[StorageProvider(ProviderName = "AzureStore")]
class PersonGrain : GrainBase<IPersonState>, IPersonGrain {
   public Task<String> Name {
      get { return Task.FromResult(State.Name); }
   }

   public Task SetName(string name) {
      State.Name = name;
      return TaskDone.Done;
   }

   public override Task DeactivateAsync() {
      State.WriteStateAsync();
      return base.DeactivateAsync();
   }
}

The storage provider, AzureStore, is configured in the OrleansConfiguration.xml file.

Some Useful Techniques for Using Tasks

The Task class provides the following convenient way to create a completed task for a specific value:

Task.FromResult(value);

The Orleans API provides the following utility property to return a completed Task.

TaskDone.Done;

The following example shows the use of Task.WhenAll() to fan-out the sending of messages allowing them to be processed simultaneously:

List<Task> promises = new List<Task>();
for (Int32 i = 0; i < 10; i++) {
   var personGrain = PersonGrainFactory.GetGrain(i);
   promises.Add(personGrain.SetName(
      String.Format(“John-{0}”, name, i)));
}
await Task.WhenAll(promises);

Summary

The Orleans framework and runtime provides an easy-to-use implementation of the Actor model for the .NET platform. The definition of an actor (or grain) requires the creation of a grain interface derived from IGrainInterface and its implementation in a class derived from GrainBase or GrainBase<T>, where T is an interface identifying data the persistence of which is handled automatically.  Given the ease with which grains can be defined and the transparent manner in which the Orleans runtime allocates them to physical nodes, Orleans simplifies the development of certain classes of distributed systems.

About these ads

About Neil Mackenzie

Azure Architect at Satory Global.
This entry was posted in Azure, Orleans and tagged , . Bookmark the permalink.

2 Responses to A Second Look at Project Orleans

  1. Sergey Bykov says:

    Reminders are actually fully functional. The note about them being a work in progress was a year old. I just took it out. Some pieces of the documentation are behind the reality. :-) We’ll be fixing them.

    Thanks,
    Sergey

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s