Skip to content

Commit

Permalink
Add value type support to IActivator #8682 (#8683)
Browse files Browse the repository at this point in the history
  • Loading branch information
AdrianoAE authored Oct 25, 2023
1 parent bf0112d commit e5790ec
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 55 deletions.
17 changes: 4 additions & 13 deletions src/Orleans.Runtime/Storage/StateStorageBridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class StateStorageBridge<TState> : IStorage<TState>, IGrainMigrationParti
private readonly IGrainContext _grainContext;
private readonly IGrainStorage _store;
private readonly ILogger _logger;
private readonly IActivator<TState>? _activator;
private readonly IActivator<TState> _activator;
private GrainState<TState>? _grainState;

/// <inheritdoc/>
Expand All @@ -42,7 +42,7 @@ public TState State
}
}

private GrainState<TState> GrainState => _grainState ??= new GrainState<TState>(CreateInstance());
private GrainState<TState> GrainState => _grainState ??= new GrainState<TState>(_activator.Create());
internal bool IsStateInitialized => _grainState != null;

/// <inheritdoc/>
Expand All @@ -63,11 +63,7 @@ public StateStorageBridge(string name, IGrainContext grainContext, IGrainStorage
_name = name;
_grainContext = grainContext;
_store = store;

if (!typeof(TState).IsValueType)
{
_activator = activatorProvider.GetActivator<TState>();
}
_activator = activatorProvider.GetActivator<TState>();
}

/// <inheritdoc />
Expand Down Expand Up @@ -119,7 +115,7 @@ public async Task ClearStateAsync()
sw.Stop();

// Reset the in-memory copy of the state
GrainState.State = CreateInstance();
GrainState.State = _activator.Create();

// Update counters
StorageInstruments.OnStorageDelete(sw.Elapsed);
Expand Down Expand Up @@ -179,10 +175,5 @@ private void OnError(Exception exception, ErrorCode id, string operation)

ExceptionDispatchInfo.Throw(exception);
}

private TState CreateInstance()
=> _activator is not null
? _activator.Create()
: Activator.CreateInstance<TState>();
}
}
24 changes: 20 additions & 4 deletions src/Orleans.Serialization/Activators/DefaultActivator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

namespace Orleans.Serialization.Activators
{
internal sealed class DefaultActivator<T> : IActivator<T> where T : class
internal abstract class DefaultActivator<T> : IActivator<T>
{
private static readonly Func<T> DefaultConstructorFunction = Init();
private readonly Func<T> _constructor = DefaultConstructorFunction;
private readonly Type _type = typeof(T);
protected readonly Func<T> Constructor = DefaultConstructorFunction;
protected readonly Type Type = typeof(T);

private static Func<T> Init()
{
Expand All @@ -23,6 +23,22 @@ private static Func<T> Init()
return (Func<T>)method.CreateDelegate(typeof(Func<T>));
}

public T Create() => _constructor is { } ctor ? ctor() : Unsafe.As<T>(RuntimeHelpers.GetUninitializedObject(_type));
public abstract T Create();
}

internal sealed class DefaultReferenceTypeActivator<T> : DefaultActivator<T> where T : class
{
public override T Create()
=> Constructor is { } ctor
? ctor()
: Unsafe.As<T>(RuntimeHelpers.GetUninitializedObject(Type));
}

internal sealed class DefaultValueTypeActivator<T> : DefaultActivator<T> where T : struct
{
public override T Create()
=> Constructor is { } ctor
? ctor()
: (T)RuntimeHelpers.GetUninitializedObject(Type);
}
}
9 changes: 8 additions & 1 deletion src/Orleans.Serialization/Serializers/CodecProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,14 @@ private object GetActivatorInner(Type concreteType, Type searchType)

if (!_activators.TryGetValue(searchType, out var activatorType))
{
activatorType = typeof(DefaultActivator<>).MakeGenericType(concreteType);
if (searchType.IsValueType)
{
activatorType = typeof(DefaultValueTypeActivator<>).MakeGenericType(concreteType);
}
else
{
activatorType = typeof(DefaultReferenceTypeActivator<>).MakeGenericType(concreteType);
}
}
else if (activatorType.IsGenericTypeDefinition)
{
Expand Down
62 changes: 53 additions & 9 deletions test/DefaultCluster.Tests/SerializationTests/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,33 +22,77 @@ public void Serialization_LargeTestData()
data.SetBit(13);
data.SetEnemy(17, CampaignEnemyTestType.Enemy1);

object obj = this.HostedCluster.DeepCopy(data);
object obj = HostedCluster.DeepCopy(data);
Assert.IsAssignableFrom<LargeTestData>(obj);

object copy = this.HostedCluster.RoundTripSerializationForTesting(obj);
object copy = HostedCluster.RoundTripSerializationForTesting(obj);
Assert.IsAssignableFrom<LargeTestData>(copy);
}

[Fact, TestCategory("BVT"), TestCategory("Serialization")]
public void Serialization_ValueTypePhase1()
public void Serialization_ValueType_Phase1()
{
ValueTypeTestData data = new ValueTypeTestData(4);

object obj = this.HostedCluster.DeepCopy(data);
object obj = HostedCluster.DeepCopy(data);

Assert.IsAssignableFrom<ValueTypeTestData>(obj);
Assert.Equal<int>(4, ((ValueTypeTestData)obj).Value);
Assert.Equal(4, ((ValueTypeTestData)obj).Value);
}

[Fact, TestCategory("Serialization")]
public void Serialization_ValueTypePhase2()
[Fact, TestCategory("BVT"), TestCategory("Serialization")]
public void Serialization_ValueType_Phase2()
{
ValueTypeTestData data = new ValueTypeTestData(4);

object copy = this.HostedCluster.RoundTripSerializationForTesting(data);
object copy = HostedCluster.RoundTripSerializationForTesting(data);

Assert.IsAssignableFrom<ValueTypeTestData>(copy);
Assert.Equal<int>(4, ((ValueTypeTestData)copy).Value);
Assert.Equal(4, ((ValueTypeTestData)copy).Value);
}

[Fact, TestCategory("BVT"), TestCategory("Serialization")]
public void Serialization_DefaultActivatorValueTypeWithRequiredField_Phase1()
{
DefaultActivatorValueTypeWithRequiredField data = new(4);

object obj = HostedCluster.DeepCopy(data);

Assert.IsAssignableFrom<DefaultActivatorValueTypeWithRequiredField>(obj);
Assert.Equal(4, ((DefaultActivatorValueTypeWithRequiredField)obj).Value);
}

[Fact, TestCategory("BVT"), TestCategory("Serialization")]
public void Serialization_DefaultActivatorValueTypeWithRequiredField_Phase2()
{
DefaultActivatorValueTypeWithRequiredField data = new(4);

object copy = HostedCluster.RoundTripSerializationForTesting(data);

Assert.IsAssignableFrom<DefaultActivatorValueTypeWithRequiredField>(copy);
Assert.Equal(4, ((DefaultActivatorValueTypeWithRequiredField)copy).Value);
}

[Fact, TestCategory("BVT"), TestCategory("Serialization")]
public void Serialization_DefaultActivatorValueTypeWithUseActivator_Phase1()
{
DefaultActivatorValueTypeWithUseActivator data = new();

object obj = HostedCluster.DeepCopy(data);

Assert.IsAssignableFrom<DefaultActivatorValueTypeWithUseActivator>(obj);
Assert.Equal(4, ((DefaultActivatorValueTypeWithUseActivator)obj).Value);
}

[Fact, TestCategory("BVT"), TestCategory("Serialization")]
public void Serialization_DefaultActivatorValueTypeWithUseActivator_Phase2()
{
DefaultActivatorValueTypeWithUseActivator data = new();

object copy = HostedCluster.RoundTripSerializationForTesting(data);

Assert.IsAssignableFrom<DefaultActivatorValueTypeWithUseActivator>(copy);
Assert.Equal(4, ((DefaultActivatorValueTypeWithUseActivator)copy).Value);
}
}
}
49 changes: 38 additions & 11 deletions test/Grains/TestGrainInterfaces/IValueTypeTestGrain.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Orleans.Concurrency;
using ProtoBuf;

Expand All @@ -19,7 +20,7 @@ public ValueTypeTestData(int i)

[Serializable]
[GenerateSerializer]
public enum TestEnum : byte
public enum TestEnum : byte
{
First,
Second,
Expand All @@ -37,15 +38,15 @@ public enum CampaignEnemyTestType : sbyte
Enemy3
}

[GenerateSerializer]
public class ClassWithEnumTestData
{
[Id(0)]
public TestEnum EnumValue { get; set; }
[GenerateSerializer]
public class ClassWithEnumTestData
{
[Id(0)]
public TestEnum EnumValue { get; set; }

[Id(1)]
public CampaignEnemyTestType Enemy { get; set; }
}
[Id(1)]
public CampaignEnemyTestType Enemy { get; set; }
}

[ProtoContract]
[Serializable]
Expand Down Expand Up @@ -107,7 +108,6 @@ public int GetNumber(string name)
return stringIntDict[name];
}


// This class is not actually used anywhere. It is here to test that the serializer generator properly handles
// nested generic classes. If it doesn't, then the generated serializer for this class will fail to compile.
[Serializable]
Expand Down Expand Up @@ -213,4 +213,31 @@ public struct StructWithEmbeddedImmutable
[Id(1), Immutable] public byte[] Immutable;
[Id(2)] public byte[] Mutable;
}
}

[Serializable]
[GenerateSerializer]
public struct DefaultActivatorValueTypeWithRequiredField
{
[Id(0)] public required int Value;

[SetsRequiredMembers]
public DefaultActivatorValueTypeWithRequiredField(int value)
{
Value = value;
}
}

[UseActivator]
[Serializable]
[GenerateSerializer]
public struct DefaultActivatorValueTypeWithUseActivator
{
[Id(0)] public int Value;

[SetsRequiredMembers]
public DefaultActivatorValueTypeWithUseActivator()
{
Value = 4;
}
}
}
22 changes: 13 additions & 9 deletions test/Grains/TestInternalGrains/PersistenceTestGrains.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
using Orleans.Configuration;
using Orleans.Runtime;
using Orleans.Serialization;
using Orleans.Storage;
using UnitTests.GrainInterfaces;
using Xunit;
using Orleans.Storage;

namespace UnitTests.Grains
{
Expand Down Expand Up @@ -1116,33 +1116,37 @@ public async Task<ExternalTypeWithoutPublicConstructor> GetState()

public sealed class ExternalTypeWithoutPublicConstructor
{
public int Field { get; set; }
public int Field1 { get; set; }
public int Field2 { get; set; }

public static ExternalTypeWithoutPublicConstructor Create(int field)
=> new(field);
public static ExternalTypeWithoutPublicConstructor Create(int field1, int field2)
=> new(field1, field2);

private ExternalTypeWithoutPublicConstructor(int field)
private ExternalTypeWithoutPublicConstructor(int field1, int field2)
{
Field = field;
Field1 = field1;
Field2 = field2;
}
}

[GenerateSerializer]
public struct ExternalTypeWithoutPublicConstructorSurrogate
{
[Id(0)] public int Field;
[Id(0)] public int Field1;
[Id(1)] public required int Field2;
}

[RegisterConverter]
public sealed class ExternalTypeWithoutPublicConstructorSurrogateConverter : IConverter<ExternalTypeWithoutPublicConstructor, ExternalTypeWithoutPublicConstructorSurrogate>
{
public ExternalTypeWithoutPublicConstructor ConvertFromSurrogate(in ExternalTypeWithoutPublicConstructorSurrogate surrogate)
=> ExternalTypeWithoutPublicConstructor.Create(surrogate.Field);
=> ExternalTypeWithoutPublicConstructor.Create(surrogate.Field1, surrogate.Field2);

public ExternalTypeWithoutPublicConstructorSurrogate ConvertToSurrogate(in ExternalTypeWithoutPublicConstructor value)
=> new()
{
Field = value.Field,
Field1 = value.Field1,
Field2 = value.Field2,
};
}

Expand Down
17 changes: 9 additions & 8 deletions test/TesterInternal/StorageTests/PersistenceGrainTests.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
//#define REREAD_STATE_AFTER_WRITE_FAILED

using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Orleans.Configuration;
using Orleans.Internal;
using Orleans.Runtime;
using Orleans.Storage;
using Orleans.TestingHost;
using TesterInternal;
using TestExtensions;
using UnitTests.GrainInterfaces;
using UnitTests.Grains;
using Xunit;
using Xunit.Abstractions;
using TesterInternal;
using TestExtensions;
using Orleans.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Orleans.Configuration;

// ReSharper disable RedundantAssignment
// ReSharper disable UnusedVariable
Expand Down Expand Up @@ -1190,12 +1190,13 @@ public async Task SurrogatePersistence_TypeWithoutPublicConstructor_Read()
{
ISurrogateStateForTypeWithoutPublicConstructorGrain<ExternalTypeWithoutPublicConstructor> grain = HostedCluster.GrainFactory
.GetGrain<ISurrogateStateForTypeWithoutPublicConstructorGrain<ExternalTypeWithoutPublicConstructor>>(Guid.NewGuid());
ExternalTypeWithoutPublicConstructor instance = ExternalTypeWithoutPublicConstructor.Create(1);
ExternalTypeWithoutPublicConstructor instance = ExternalTypeWithoutPublicConstructor.Create(1, 2);

await grain.SetState(instance);
ExternalTypeWithoutPublicConstructor val = await grain.GetState();

Assert.Equal(1, val.Field);
Assert.Equal(1, val.Field1);
Assert.Equal(2, val.Field2);
}

[Fact, TestCategory("Functional"), TestCategory("Persistence")]
Expand Down

0 comments on commit e5790ec

Please sign in to comment.