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

Fix PooledArrayBufferWriter.AsMemory #8300

Merged
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
182 changes: 182 additions & 0 deletions src/Orleans.Serialization.TestKit/FieldCodecTester.cs
Expand Up @@ -14,6 +14,7 @@
using Xunit;
using Orleans.Serialization.Serializers;
using Xunit.Abstractions;
using System.Threading;

namespace Orleans.Serialization.TestKit
{
Expand Down Expand Up @@ -438,6 +439,13 @@ void Test(TValue original)
Assert.Equal(expected, result);
}

{
var writer = Writer.CreatePooled(_sessionPool.GetSession());
serializer.Serialize(original, ref writer);
var result = writer.Output.ToArray();
Assert.Equal(expected, result);
}

var bytes = new byte[10240];

{
Expand Down Expand Up @@ -497,6 +505,180 @@ void Test(TValue original)
}
}

[Fact]
public void CanRoundTripCollectionViaSerializer()
{
var serializer = _serviceProvider.GetRequiredService<Serializer<List<TValue>>>();

var original = new List<TValue>();
original.AddRange(TestValues);
for (var i = 0; i < 5; i++)
{
original.Add(CreateValue());
}

using var writerSession = _sessionPool.GetSession();
var writer = Writer.CreatePooled(writerSession);
serializer.Serialize(original, ref writer);
using var readerSession = _sessionPool.GetSession();
var reader = Reader.Create(writer.Output.AsReadOnlySequence(), readerSession);
var deserialized = serializer.Deserialize(ref reader);

Assert.Equal(original.Count, deserialized.Count);
for (var i = 0; i < original.Count; ++i)
{
var isEqual = Equals(original[i], deserialized[i]);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Deserialized value at index {i}, \"{deserialized}\", must equal original value, \"{original}\"");
}

Assert.Equal(writer.Position, reader.Position);
Assert.Equal(writerSession.ReferencedObjects.CurrentReferenceId, readerSession.ReferencedObjects.CurrentReferenceId);
}

[Fact]
public void CanCopyCollectionViaSerializer()
{
var copier = _serviceProvider.GetRequiredService<DeepCopier<List<TValue>>>();

var original = new List<TValue>();
original.AddRange(TestValues);
for (var i = 0; i < 5; i++)
{
original.Add(CreateValue());
}

var copy = copier.Copy(original);

Assert.Equal(original.Count, copy.Count);
for (var i = 0; i < original.Count; ++i)
{
var isEqual = Equals(original[i], copy[i]);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Copied value at index {i}, \"{copy}\", must equal original value, \"{original}\"");
}
}

[Fact]
public void CanCopyCollectionViaUntypedSerializer()
{
var copier = _serviceProvider.GetRequiredService<DeepCopier<List<object>>>();

var original = new List<object>();
foreach (var value in TestValues)
{
original.Add(value);
}

for (var i = 0; i < 5; i++)
{
original.Add(CreateValue());
}

var copy = copier.Copy(original);

Assert.Equal(original.Count, copy.Count);
for (var i = 0; i < original.Count; ++i)
{
var isEqual = Equals((TValue)original[i], (TValue)copy[i]);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Copied value at index {i}, \"{copy}\", must equal original value, \"{original}\"");
}
}

[Fact]
public void CanCopyTupleViaSerializer_Untyped()
{
var copier = _serviceProvider.GetRequiredService<DeepCopier<(string, object, object, string)>>();
var value = TestValues.Reverse().Concat(new[] { CreateValue(), CreateValue() }).Take(2).ToArray();

var original = (Guid.NewGuid().ToString(), (object)value[0], (object)value[1], Guid.NewGuid().ToString());

var copy = copier.Copy(original);

var isEqual = Equals(original.Item1, copy.Item1);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Copied value for item 1, \"{copy.Item1}\", must equal original value, \"{original.Item1}\"");
isEqual = Equals((TValue)original.Item2, (TValue)copy.Item2);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Copied value for item 2, \"{copy.Item2}\", must equal original value, \"{original.Item2}\"");
isEqual = Equals((TValue)original.Item3, (TValue)copy.Item3);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Copied value for item 3, \"{copy.Item3}\", must equal original value, \"{original.Item3}\"");
isEqual = Equals(original.Item4, copy.Item4);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Copied value for item 4, \"{copy.Item4}\", must equal original value, \"{original.Item4}\"");
}

[Fact]
public void CanCopyTupleViaSerializer()
{
var copier = _serviceProvider.GetRequiredService<DeepCopier<(string, TValue, TValue, string)>>();

var original = (Guid.NewGuid().ToString(), CreateValue(), CreateValue(), Guid.NewGuid().ToString());

var copy = copier.Copy(original);

var isEqual = Equals(original.Item1, copy.Item1);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Copied value for item 1, \"{copy}\", must equal original value, \"{original}\"");
isEqual = Equals(original.Item2, copy.Item2);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Copied value for item 2, \"{copy}\", must equal original value, \"{original}\"");
isEqual = Equals(original.Item3, copy.Item3);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Copied value for item 3, \"{copy}\", must equal original value, \"{original}\"");
isEqual = Equals(original.Item4, copy.Item4);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Copied value for item 4, \"{copy}\", must equal original value, \"{original}\"");
}

[Fact]
public void CanRoundTripTupleViaSerializer()
{
var serializer = _serviceProvider.GetRequiredService<Serializer<(string, TValue, TValue, string)>>();

var original = (Guid.NewGuid().ToString(), CreateValue(), CreateValue(), Guid.NewGuid().ToString());

using var writerSession = _sessionPool.GetSession();
var writer = Writer.CreatePooled(writerSession);
serializer.Serialize(original, ref writer);
using var readerSession = _sessionPool.GetSession();
var reader = Reader.Create(writer.Output.AsReadOnlySequence(), readerSession);
var deserialized = serializer.Deserialize(ref reader);

var isEqual = Equals(original.Item1, deserialized.Item1);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Deserialized value for item 1, \"{deserialized}\", must equal original value, \"{original}\"");
isEqual = Equals(original.Item2, deserialized.Item2);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Deserialized value for item 2, \"{deserialized}\", must equal original value, \"{original}\"");
isEqual = Equals(original.Item3, deserialized.Item3);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Deserialized value for item 3, \"{deserialized}\", must equal original value, \"{original}\"");
isEqual = Equals(original.Item4, deserialized.Item4);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Deserialized value for item 4, \"{deserialized}\", must equal original value, \"{original}\"");

Assert.Equal(writer.Position, reader.Position);
Assert.Equal(writerSession.ReferencedObjects.CurrentReferenceId, readerSession.ReferencedObjects.CurrentReferenceId);
}

[Fact]
public void CanRoundTripViaSerializer()
{
Expand Down
Expand Up @@ -226,7 +226,7 @@ public Memory<byte> AsMemory(int offset)
#if NET6_0_OR_GREATER
if (IsStandardSize)
{
return MemoryMarshal.CreateFromPinnedArray(Array, offset, Array.Length);
return MemoryMarshal.CreateFromPinnedArray(Array, offset, Array.Length - offset);
}
#endif

Expand Down
13 changes: 11 additions & 2 deletions src/Orleans.Serialization/Serializers/CodecProvider.cs
Expand Up @@ -637,10 +637,19 @@ private IDeepCopier CreateCopierInstance(Type fieldType, Type searchType)
{
copierType = surrogateCodecType;
}
else if (searchType.BaseType is { } baseType && CreateCopierInstance(fieldType, baseType) is IDerivedTypeCopier baseCopier)
else if (searchType.BaseType is { } baseType)
{
// Find copiers which generalize over all subtypes.
return baseCopier;
if (CreateCopierInstance(fieldType, baseType) is IDerivedTypeCopier baseCopier)
{
return baseCopier;
}
else if (baseType.IsGenericType
&& baseType.IsConstructedGenericType
&& CreateCopierInstance(fieldType, baseType.GetGenericTypeDefinition()) is IDerivedTypeCopier genericBaseCopier)
{
return genericBaseCopier;
}
}

return copierType != null ? (IDeepCopier)GetServiceOrCreateInstance(copierType, constructorArguments) : null;
Expand Down
41 changes: 24 additions & 17 deletions test/Orleans.Serialization.UnitTests/JsonSerializerTests.cs
@@ -1,4 +1,11 @@
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Orleans.Serialization.Cloning;
using Orleans.Serialization.Codecs;
Expand All @@ -10,7 +17,7 @@
namespace Orleans.Serialization.UnitTests
{
[Trait("Category", "BVT")]
public class JsonCodecTests : FieldCodecTester<MyJsonClass, IFieldCodec<MyJsonClass>>
public class JsonCodecTests : FieldCodecTester<MyJsonClass?, IFieldCodec<MyJsonClass?>>
{
public JsonCodecTests(ITestOutputHelper output) : base(output)
{
Expand All @@ -21,20 +28,20 @@ protected override void Configure(ISerializerBuilder builder)
builder.AddJsonSerializer(isSupported: type => type.GetCustomAttribute<MyJsonSerializableAttribute>(inherit: false) is not null);
}

protected override MyJsonClass CreateValue() => new MyJsonClass { IntProperty = 30, SubTypeProperty = "hello" };
protected override MyJsonClass? CreateValue() => new MyJsonClass { IntProperty = 30, SubTypeProperty = "hello", Id = new(Guid.NewGuid()) };

protected override MyJsonClass[] TestValues => new MyJsonClass[]
protected override MyJsonClass?[] TestValues => new MyJsonClass?[]
{
null,
new MyJsonClass(),
new MyJsonClass() { IntProperty = 150, SubTypeProperty = new string('c', 20) },
new MyJsonClass() { IntProperty = -150_000, SubTypeProperty = new string('c', 6_000) },
new MyJsonClass() { Id = new(Guid.NewGuid()) },
new MyJsonClass() { IntProperty = 150, SubTypeProperty = new string('c', 20), Id = new(Guid.NewGuid()) },
new MyJsonClass() { IntProperty = -150_000, SubTypeProperty = new string('c', 6_000), Id = new(Guid.NewGuid()) },
};

[Fact]
public void JsonSerializerDeepCopyTyped()
{
var original = new MyJsonClass { IntProperty = 30, SubTypeProperty = "hi" };
var original = new MyJsonClass { IntProperty = 30, SubTypeProperty = "hi", Id = new(Guid.NewGuid()) };
var copier = ServiceProvider.GetRequiredService<DeepCopier<MyJsonClass>>();
var result = copier.Copy(original);

Expand All @@ -45,7 +52,7 @@ public void JsonSerializerDeepCopyTyped()
[Fact]
public void JsonSerializerDeepCopyUntyped()
{
var original = new MyJsonClass { IntProperty = 30, SubTypeProperty = "hi" };
var original = new MyJsonClass { IntProperty = 30, SubTypeProperty = "hi", Id = new(Guid.NewGuid()) };
var copier = ServiceProvider.GetRequiredService<DeepCopier>();
var result = (MyJsonClass)copier.Copy((object)original);

Expand All @@ -56,7 +63,7 @@ public void JsonSerializerDeepCopyUntyped()
[Fact]
public void JsonSerializerRoundTripThroughCodec()
{
var original = new MyJsonClass { IntProperty = 30, SubTypeProperty = "hi" };
var original = new MyJsonClass { IntProperty = 30, SubTypeProperty = "hi", Id = new(Guid.NewGuid()) };
var result = RoundTripThroughCodec(original);

Assert.Equal(original.IntProperty, result.IntProperty);
Expand All @@ -66,7 +73,7 @@ public void JsonSerializerRoundTripThroughCodec()
[Fact]
public void JsonSerializerRoundTripThroughUntypedSerializer()
{
var original = new MyJsonClass { IntProperty = 30, SubTypeProperty = "hi" };
var original = new MyJsonClass { IntProperty = 30, SubTypeProperty = "hi", Id = new(Guid.NewGuid()) };
var untypedResult = RoundTripThroughUntypedSerializer(original, out _);

var result = Assert.IsType<MyJsonClass>(untypedResult);
Expand All @@ -76,7 +83,7 @@ public void JsonSerializerRoundTripThroughUntypedSerializer()
}

[Trait("Category", "BVT")]
public class JsonCodecCopierTests : CopierTester<MyJsonClass, IDeepCopier<MyJsonClass>>
public class JsonCodecCopierTests : CopierTester<MyJsonClass?, IDeepCopier<MyJsonClass?>>
{
public JsonCodecCopierTests(ITestOutputHelper output) : base(output)
{
Expand All @@ -86,17 +93,17 @@ protected override void Configure(ISerializerBuilder builder)
{
builder.AddJsonSerializer(isSupported: type => type.GetCustomAttribute<MyJsonSerializableAttribute>(inherit: false) is not null);
}
protected override IDeepCopier<MyJsonClass> CreateCopier() => ServiceProvider.GetRequiredService<ICodecProvider>().GetDeepCopier<MyJsonClass>();
protected override IDeepCopier<MyJsonClass?> CreateCopier() => ServiceProvider.GetRequiredService<ICodecProvider>().GetDeepCopier<MyJsonClass?>();


protected override MyJsonClass CreateValue() => new MyJsonClass { IntProperty = 30, SubTypeProperty = "hello" };
protected override MyJsonClass? CreateValue() => new MyJsonClass { IntProperty = 30, SubTypeProperty = "hello", Id = new(Guid.NewGuid()) };

protected override MyJsonClass[] TestValues => new MyJsonClass[]
protected override MyJsonClass?[] TestValues => new MyJsonClass?[]
{
null,
new MyJsonClass(),
new MyJsonClass() { IntProperty = 150, SubTypeProperty = new string('c', 20) },
new MyJsonClass() { IntProperty = -150_000, SubTypeProperty = new string('c', 6_000) },
new MyJsonClass() { Id = new(Guid.NewGuid()) },
new MyJsonClass() { IntProperty = 150, SubTypeProperty = new string('c', 20), Id = new(Guid.NewGuid()) },
new MyJsonClass() { IntProperty = -150_000, SubTypeProperty = new string('c', 6_000), Id = new(Guid.NewGuid()) },
};
}
}