Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Eliminated GrainState #1060

Merged
merged 3 commits into from
Jan 4, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 6 additions & 13 deletions src/Orleans/CodeGeneration/GrainState.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
//#define REREAD_STATE_AFTER_WRITE_FAILED

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Reflection;
using System.Threading.Tasks;
using Orleans.Runtime;
using Orleans.Serialization;
using Orleans.Storage;

namespace Orleans
{
/// <summary>
/// Base class for generated grain state classes.
/// </summary>
[Serializable]
[Obsolete]
public abstract class GrainState
{
/// <summary>
Expand Down Expand Up @@ -103,7 +96,7 @@ protected GrainState(string grainTypeFullName)
var result = new Dictionary<string, object>();

var properties = this.GetType().GetProperties();
foreach(var property in properties)
foreach (var property in properties)
if (property.Name != "Etag")
result[property.Name] = property.GetValue(this);

Expand All @@ -129,12 +122,12 @@ public virtual void SetAll(IDictionary<string, object> values)
{
var property = type.GetProperty(key);
// property doesn't have setter
if (property.GetSetMethod() == null) {continue;}
if (property.GetSetMethod() == null) { continue; }
property.SetValue(this, values[key]);
}
}


/// <summary>
/// Resets properties of the state object to their default values.
/// </summary>
Expand All @@ -144,9 +137,9 @@ private void ResetProperties()
foreach (var property in properties)
{
// property doesn't have setter
if (property.GetSetMethod() == null) {continue;}
if (property.GetSetMethod() == null) { continue; }
property.SetValue(this, null);
}
}
}
}
}
45 changes: 45 additions & 0 deletions src/Orleans/CodeGeneration/IGrainState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;

namespace Orleans
{
public interface IGrainState
{
object State { get; set; }
string ETag { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should ETag be here? I think the storage providers which need an ETag should create their own implementation of this interface which includes one. We should give storage providers a CreateGrainState() method (or some such) which returns a new instance of the IGrainState implementation used by the storage provider.

The storage provider can mutate the ETag on its implementation instead of returning Task<string>.

}

[Serializable]
internal class GrainState<T> : IGrainState
{
public T State;

object IGrainState.State
{
get
{
return State;

}
set
{
State = (T)value;
}
}

public string ETag { get; set; }

public GrainState()
{
}

public GrainState(T state) : this(state, null)
{
}

public GrainState(T state, string eTag)
{
State = state;
ETag = eTag;
}
}
}
12 changes: 1 addition & 11 deletions src/Orleans/CodeGeneration/TypeUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -419,17 +419,7 @@ public static bool IsGrainMethodInvokerType(Type type)
}
return generalType.GetTypeInfo().IsAssignableFrom(type) && TypeHasAttribute(type, typeof(MethodInvokerAttribute));
}

public static bool IsGrainStateType(Type type)
{
var generalType = typeof(GrainState);
if (type.Assembly.ReflectionOnly)
{
generalType = ToReflectionOnlyType(generalType);
}
return generalType.GetTypeInfo().IsAssignableFrom(type) && TypeHasAttribute(type, typeof(GrainStateAttribute));
}


public static Type ResolveType(string fullName)
{
return CachedTypeResolver.Instance.ResolveType(fullName);
Expand Down
36 changes: 23 additions & 13 deletions src/Orleans/Core/Grain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ public abstract class Grain : IAddressable
{
private IGrainRuntime runtime;

internal GrainState GrainState { get; set; }

internal IStorage Storage { get; set; }

// Do not use this directly because we currently don't provide a way to inject it;
// any interaction with it will result in non unit-testable code. Any behaviour that can be accessed
// from within client code (including subclasses of this class), should be exposed through IGrainRuntime.
Expand Down Expand Up @@ -258,16 +254,19 @@ internal string CaptureRuntimeEnvironment()
/// Base class for a Grain with declared persistent state.
/// </summary>
/// <typeparam name="TGrainState">The class of the persistent state object</typeparam>
public class Grain<TGrainState> : Grain
where TGrainState : GrainState
public class Grain<TGrainState> : Grain, IStatefulGrain
{
private readonly GrainState<TGrainState> grainState;

private IStorage storage;

/// <summary>
/// This constructor should never be invoked. We expose it so that client code (subclasses of this class) do not have to add a constructor.
/// Client code should use the GrainFactory to get a reference to a Grain.
/// </summary>
protected Grain() : base()
protected Grain()
{
grainState = new GrainState<TGrainState>();
}

/// <summary>
Expand All @@ -281,31 +280,42 @@ protected Grain() : base()
protected Grain(IGrainIdentity identity, IGrainRuntime runtime, TGrainState state, IStorage storage)
: base(identity, runtime)
{
GrainState = state;
Storage = storage;
grainState = new GrainState<TGrainState>(state);
this.storage = storage;
}

/// <summary>
/// Strongly typed accessor for the grain state
/// </summary>
protected TGrainState State
{
get { return base.GrainState as TGrainState; }
get { return grainState.State; }
set { grainState.State = value; }
}

void IStatefulGrain.SetStorage(IStorage storage)
{
this.storage = storage;
}

IGrainState IStatefulGrain.GrainState
{
get { return grainState; }
}

protected virtual Task ClearStateAsync()
{
return Storage.ClearStateAsync();
return storage.ClearStateAsync();
}

protected virtual Task WriteStateAsync()
{
return Storage.WriteStateAsync();
return storage.WriteStateAsync();
}

protected virtual Task ReadStateAsync()
{
return Storage.ReadStateAsync();
return storage.ReadStateAsync();
}
}
}
22 changes: 12 additions & 10 deletions src/Orleans/Core/GrainStateStorageBridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ namespace Orleans.Core
internal class GrainStateStorageBridge : IStorage
{
private readonly IStorageProvider store;
private readonly Grain grain;
private readonly IStatefulGrain grain;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please explain what is the difference between grain and baseGrain here and why we need both?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Grain now does not possess state, so they are needed in order to have access to both grain.GrainState and baseGrain.GrainReference properties.

private readonly Grain baseGrain;
private readonly string grainTypeName;

public GrainStateStorageBridge(string grainTypeName, Grain grain, IStorageProvider store)
public GrainStateStorageBridge(string grainTypeName, IStatefulGrain grain, IStorageProvider store)
{
if (grainTypeName == null)
{
Expand All @@ -31,6 +32,7 @@ public GrainStateStorageBridge(string grainTypeName, Grain grain, IStorageProvid
}
this.grainTypeName = grainTypeName;
this.grain = grain;
this.baseGrain = grain as Grain;
this.store = store;
}

Expand All @@ -42,11 +44,11 @@ public async Task ReadStateAsync()
{
const string what = "ReadState";
Stopwatch sw = Stopwatch.StartNew();
GrainReference grainRef = grain.GrainReference;
GrainReference grainRef = baseGrain.GrainReference;
try
{
await store.ReadStateAsync(grainTypeName, grainRef, grain.GrainState);

StorageStatisticsGroup.OnStorageRead(store, grainTypeName, grainRef, sw.Elapsed);
}
catch (Exception exc)
Expand All @@ -70,12 +72,11 @@ public async Task WriteStateAsync()
{
const string what = "WriteState";
Stopwatch sw = Stopwatch.StartNew();
GrainReference grainRef = grain.GrainReference;
GrainReference grainRef = baseGrain.GrainReference;
Exception errorOccurred;
try
{
await store.WriteStateAsync(grainTypeName, grainRef, grain.GrainState);

StorageStatisticsGroup.OnStorageWrite(store, grainTypeName, grainRef, sw.Elapsed);
errorOccurred = null;
}
Expand Down Expand Up @@ -125,13 +126,14 @@ public async Task ClearStateAsync()
{
const string what = "ClearState";
Stopwatch sw = Stopwatch.StartNew();
GrainReference grainRef = grain.GrainReference;
GrainReference grainRef = baseGrain.GrainReference;
try
{
// Clear (most likely Delete) state from external storage
await store.ClearStateAsync(grainTypeName, grainRef, grain.GrainState);
// Null out the in-memory copy of the state
grain.GrainState.SetAll(null);

// Reset the in-memory copy of the state
grain.GrainState.State = Activator.CreateInstance(grain.GrainState.State.GetType());

// Update counters
StorageStatisticsGroup.OnStorageDelete(store, grainTypeName, grainRef, sw.Elapsed);
Expand Down Expand Up @@ -159,7 +161,7 @@ private string MakeErrorMsg(string what, Exception exc)
if(decoder != null)
decoder.DecodeException(exc, out httpStatusCode, out errorCode, true);

GrainReference grainReference = grain.GrainReference;
GrainReference grainReference = baseGrain.GrainReference;
return string.Format("Error from storage provider during {0} for grain Type={1} Pk={2} Id={3} Error={4}" + Environment.NewLine + " {5}",
what, grainTypeName, grainReference.GrainId.ToDetailedString(), grainReference, errorCode, TraceLogger.PrintException(exc));
}
Expand Down
11 changes: 11 additions & 0 deletions src/Orleans/Core/IStatefulGrain.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Orleans.Core;

namespace Orleans
{
internal interface IStatefulGrain
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this new interface? Can't Grain have those?
Or generally, can this extra interface be avoided?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new interface makes easy to access grain state properties. I wonder if there's better way of doing this, and implementing #1060 (comment) will not remove the need in this interface.

{
IGrainState GrainState { get; }

void SetStorage(IStorage storage);
}
}
2 changes: 2 additions & 0 deletions src/Orleans/Orleans.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@
<Compile Include="CodeGeneration\OrleansCodeGenerationTargetAttribute.cs" />
<Compile Include="CodeGeneration\KnownTypeAttribute.cs" />
<Compile Include="CodeGeneration\SkipCodeGenerationAttribute.cs" />
<Compile Include="CodeGeneration\IGrainState.cs" />
<Compile Include="Core\IStatefulGrain.cs" />
<Compile Include="Providers\DefaultServiceProvider.cs" />
<Compile Include="Serialization\IExternalSerializer.cs" />
<Compile Include="Configuration\LimitNames.cs" />
Expand Down
20 changes: 7 additions & 13 deletions src/Orleans/Providers/IMemoryStorageGrain.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Orleans.Storage
Expand All @@ -12,24 +10,20 @@ public interface IMemoryStorageGrain : IGrainWithIntegerKey
/// <summary>
/// Async method to cause retrieval of the specified grain state data from memory store.
/// </summary>
/// <param name="stateStore">The name of the store that is used to store this grain state</param>
/// <param name="grainStoreKey">Store key for this grain.</param>
/// <returns>Value promise for the currently stored grain state for the specified grain and the etag of this data.</returns>
Task<Tuple<IDictionary<string, object>, string>> ReadStateAsync(string stateStore, string grainStoreKey);
/// <param name="grainType">Type of this grain [fully qualified class name]</param>
/// <param name="grainId">Grain id for this grain.</param>
/// <returns>Value promise for the currently stored grain state for the specified grain.</returns>
Task<IGrainState> ReadStateAsync(string stateStore, string grainStoreKey);

/// <summary>
/// Async method to cause update of the specified grain state data into memory store.
/// </summary>
/// <param name="stateStore">The name of the store that is used to store this grain state</param>
/// <param name="grainStoreKey">Store key for this grain.</param>
/// <param name="grainState">New state data to be stored for this grain.</param>
/// <param name="eTag">The previous etag that was read.</param>
/// <returns>Value promise of the etag of the update operation for stored grain state for the specified grain.</returns>
Task<string> WriteStateAsync(string stateStore, string grainStoreKey, IDictionary<string, object> grainState, string eTag);

/// <summary>
/// Async method to cause deletion of the specified grain state data from memory store.
/// </summary>
/// <returns>Completion promise with new eTag for the update operation for stored grain state for the specified grain.</returns>
Task<string> WriteStateAsync(string grainType, string grainId, IGrainState grainState);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be returning the ETag? - ETags aren't a concept which all storage systems have.
EDIT: Given IMemoryStorageGrain is here to support a specific implementation, it's fine

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ReubenBond Why are you concerned? MemoryStorageGrain is just an internal implementation detail of MemoryStorage which is an "emulator" storage provider intended only for development scenarios.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the a comment to say something to that effect :) Thanks


/// <param name="stateStore">The name of the store that is used to store this grain state</param>
/// <param name="grainStoreKey">Store key for this grain.</param>
/// <param name="eTag">The previous etag that was read.</param>
Expand Down
7 changes: 3 additions & 4 deletions src/Orleans/Providers/IStorageProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Net;
using System.Runtime.Serialization;
using System.Threading.Tasks;

using Orleans.Runtime;
using Orleans.Providers;

Expand All @@ -23,21 +22,21 @@ public interface IStorageProvider : IProvider
/// <param name="grainReference">Grain reference object for this grain.</param>
/// <param name="grainState">State data object to be populated for this grain.</param>
/// <returns>Completion promise for the Read operation on the specified grain.</returns>
Task ReadStateAsync(string grainType, GrainReference grainReference, GrainState grainState);
Task ReadStateAsync(string grainType, GrainReference grainReference, IGrainState grainState);

/// <summary>Write data function for this storage provider instance.</summary>
/// <param name="grainType">Type of this grain [fully qualified class name]</param>
/// <param name="grainReference">Grain reference object for this grain.</param>
/// <param name="grainState">State data object to be written for this grain.</param>
/// <returns>Completion promise for the Write operation on the specified grain.</returns>
Task WriteStateAsync(string grainType, GrainReference grainReference, GrainState grainState);
Task WriteStateAsync(string grainType, GrainReference grainReference, IGrainState grainState);

/// <summary>Delete / Clear data function for this storage provider instance.</summary>
/// <param name="grainType">Type of this grain [fully qualified class name]</param>
/// <param name="grainReference">Grain reference object for this grain.</param>
/// <param name="grainState">Copy of last-known state data object for this grain.</param>
/// <returns>Completion promise for the Delete operation on the specified grain.</returns>
Task ClearStateAsync(string grainType, GrainReference grainReference, GrainState grainState);
Task ClearStateAsync(string grainType, GrainReference grainReference, IGrainState grainState);
}

/// <summary>
Expand Down