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: Don't create new ContractResolver on every serialization/deserialization #609

Merged
merged 2 commits into from
Oct 16, 2020
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions src/Akavache.Core/BlobCache/InMemoryBlobCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class InMemoryBlobCache : ISecureBlobCache, IObjectBlobCache, IEnableLogg
private Dictionary<string, CacheEntry> _cache = new Dictionary<string, CacheEntry>();
private bool _disposed;
private DateTimeKind? _dateTimeKind;
private JsonDateTimeContractResolver _jsonDateTimeContractResolver = new JsonDateTimeContractResolver(); // This will make us use ticks instead of json ticks for DateTime.

/// <summary>
/// Initializes a new instance of the <see cref="InMemoryBlobCache"/> class.
Expand Down Expand Up @@ -91,7 +92,16 @@ internal InMemoryBlobCache(
public DateTimeKind? ForcedDateTimeKind
{
get => _dateTimeKind ?? BlobCache.ForcedDateTimeKind;
set => _dateTimeKind = value;

set
{
_dateTimeKind = value;

if (_jsonDateTimeContractResolver != null)
{
_jsonDateTimeContractResolver.ForceDateTimeKindOverride = value;
}
}
}

/// <inheritdoc />
Expand Down Expand Up @@ -442,7 +452,8 @@ protected virtual void Dispose(bool isDisposing)
private byte[] SerializeObject<T>(T value)
{
var settings = Locator.Current.GetService<JsonSerializerSettings>() ?? new JsonSerializerSettings();
settings.ContractResolver = new JsonDateTimeContractResolver(settings.ContractResolver, ForcedDateTimeKind); // This will make us use ticks instead of json ticks for DateTime.
_jsonDateTimeContractResolver.ExistingContractResolver = settings.ContractResolver;
settings.ContractResolver = _jsonDateTimeContractResolver;
var serializer = JsonSerializer.Create(settings);
using (var ms = new MemoryStream())
{
Expand All @@ -457,7 +468,8 @@ private byte[] SerializeObject<T>(T value)
private T DeserializeObject<T>(byte[] data)
{
var settings = Locator.Current.GetService<JsonSerializerSettings>() ?? new JsonSerializerSettings();
settings.ContractResolver = new JsonDateTimeContractResolver(settings.ContractResolver, ForcedDateTimeKind); // This will make us use ticks instead of json ticks for DateTime.
_jsonDateTimeContractResolver.ExistingContractResolver = settings.ContractResolver;
settings.ContractResolver = _jsonDateTimeContractResolver;
var serializer = JsonSerializer.Create(settings);
using (var reader = new BsonDataReader(new MemoryStream(data)))
{
Expand Down
21 changes: 15 additions & 6 deletions src/Akavache.Core/Json/JsonDateTimeContractResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@ namespace Akavache
/// </summary>
internal class JsonDateTimeContractResolver : DefaultContractResolver
{
private readonly IContractResolver _existingContractResolver;
private readonly DateTimeKind? _forceDateTimeKindOverride;
/// <summary>
/// Initializes a new instance of the <see cref="JsonDateTimeContractResolver"/> class.
/// </summary>
public JsonDateTimeContractResolver()
: this(null, null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="JsonDateTimeContractResolver"/> class.
Expand All @@ -24,14 +29,18 @@ internal class JsonDateTimeContractResolver : DefaultContractResolver
/// <param name="forceDateTimeKindOverride">If we should override the <see cref="DateTimeKind"/>.</param>
public JsonDateTimeContractResolver(IContractResolver contractResolver, DateTimeKind? forceDateTimeKindOverride)
{
_existingContractResolver = contractResolver;
_forceDateTimeKindOverride = forceDateTimeKindOverride;
ExistingContractResolver = contractResolver;
ForceDateTimeKindOverride = forceDateTimeKindOverride;
}

public IContractResolver ExistingContractResolver { get; set; }

public DateTimeKind? ForceDateTimeKindOverride { get; set; }

/// <inheritdoc />
public override JsonContract ResolveContract(Type type)
{
var contract = _existingContractResolver?.ResolveContract(type);
var contract = ExistingContractResolver?.ResolveContract(type);
if (contract?.Converter != null)
{
return contract;
Expand All @@ -44,7 +53,7 @@ public override JsonContract ResolveContract(Type type)

if (type == typeof(DateTime) || type == typeof(DateTime?))
{
contract.Converter = _forceDateTimeKindOverride == DateTimeKind.Local ? JsonDateTimeTickConverter.LocalDateTimeKindDefault : JsonDateTimeTickConverter.Default;
contract.Converter = ForceDateTimeKindOverride == DateTimeKind.Local ? JsonDateTimeTickConverter.LocalDateTimeKindDefault : JsonDateTimeTickConverter.Default;
}
else if (type == typeof(DateTimeOffset) || type == typeof(DateTimeOffset?))
{
Expand Down
18 changes: 15 additions & 3 deletions src/Akavache.Sqlite3/SqlLiteCache/SqlRawPersistentBlobCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class SqlRawPersistentBlobCache : IObjectBlobCache, IEnableLogger, IObjec
private IDisposable _queueThread;
private DateTimeKind? _dateTimeKind;
private bool _disposed;
private JsonDateTimeContractResolver _jsonDateTimeContractResolver = new JsonDateTimeContractResolver(); // This will make us use ticks instead of json ticks for DateTime.

/// <summary>
/// Initializes a new instance of the <see cref="SqlRawPersistentBlobCache"/> class.
Expand All @@ -56,7 +57,16 @@ public SqlRawPersistentBlobCache(string databaseFile, IScheduler scheduler = nul
public DateTimeKind? ForcedDateTimeKind
{
get => _dateTimeKind ?? BlobCache.ForcedDateTimeKind;
set => _dateTimeKind = value;

set
{
_dateTimeKind = value;

if (_jsonDateTimeContractResolver != null)
{
_jsonDateTimeContractResolver.ForceDateTimeKindOverride = value;
}
}
}

/// <inheritdoc />
Expand Down Expand Up @@ -672,7 +682,8 @@ protected virtual IObservable<byte[]> AfterReadFromDiskFilter(byte[] data, ISche
private byte[] SerializeObject<T>(T value)
{
var settings = Locator.Current.GetService<JsonSerializerSettings>() ?? new JsonSerializerSettings();
settings.ContractResolver = new JsonDateTimeContractResolver(settings.ContractResolver, ForcedDateTimeKind); // This will make us use ticks instead of json ticks for DateTime.
_jsonDateTimeContractResolver.ExistingContractResolver = settings.ContractResolver;
settings.ContractResolver = _jsonDateTimeContractResolver;
var serializer = JsonSerializer.Create(settings);
using (var ms = new MemoryStream())
{
Expand All @@ -687,7 +698,8 @@ private byte[] SerializeObject<T>(T value)
private IObservable<T> DeserializeObject<T>(byte[] data)
{
var settings = Locator.Current.GetService<JsonSerializerSettings>() ?? new JsonSerializerSettings();
settings.ContractResolver = new JsonDateTimeContractResolver(settings.ContractResolver, ForcedDateTimeKind); // This will make us use ticks instead of json ticks for DateTime.
_jsonDateTimeContractResolver.ExistingContractResolver = settings.ContractResolver;
settings.ContractResolver = _jsonDateTimeContractResolver;
var serializer = JsonSerializer.Create(settings);
using (var reader = new BsonDataReader(new MemoryStream(data)))
{
Expand Down
7 changes: 1 addition & 6 deletions src/Akavache.Tests/TestBases/DateTimeTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@ namespace Akavache.Tests
/// </summary>
public abstract class DateTimeTestBase
{
/// <summary>
/// Time zone for when testing UTC vs local time operations. Just has to be something that doesn't match UTC.
/// </summary>
private const string TestTimeZone = "Pacific Standard Time";

/// <summary>
/// Gets the date time offsets used in theory tests.
/// </summary>
Expand Down Expand Up @@ -56,7 +51,7 @@ public abstract class DateTimeTestBase
/// <summary>
/// Gets the date time when the tests are done to keep them consistent.
/// </summary>
private static DateTime LocalTestNow { get; } = TimeZoneInfo.ConvertTimeFromUtc(TestNow.ToUniversalTime(), TimeZoneInfo.FindSystemTimeZoneById(TestTimeZone));
private static DateTime LocalTestNow { get; } = TimeZoneInfo.ConvertTimeFromUtc(TestNow.ToUniversalTime(), TimeZoneInfo.CreateCustomTimeZone("testTimeZone", TimeSpan.FromHours(6), "Test Time Zone", "Test Time Zone"));

/// <summary>
/// Gets the date time off set when the tests are done to keep them consistent.
Expand Down
40 changes: 38 additions & 2 deletions src/Akavache.Tests/UtilityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.IO;
using System.Linq;
using System.Reactive.Subjects;
using System.Runtime.InteropServices;
using Xunit;

namespace Akavache.Tests
Expand Down Expand Up @@ -36,6 +37,11 @@ public void DirectoryCreateCreatesDirectories()
[Fact]
public void DirectoryCreateThrowsIOExceptionForNonexistentNetworkPaths()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return;
}

var exception = Assert.Throws<IOException>(() => new DirectoryInfo(@"\\does\not\exist").CreateRecursive());
Assert.StartsWith("The network path was not found", exception.Message);
}
Expand All @@ -46,7 +52,21 @@ public void DirectoryCreateThrowsIOExceptionForNonexistentNetworkPaths()
[Fact]
public void UtilitySplitsAbsolutePaths()
{
Assert.Equal(new[] { @"c:\", "foo", "bar" }, new DirectoryInfo(@"c:\foo\bar").SplitFullPath());
string path;
string expectedRoot;

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
path = @"c:\foo\bar";
expectedRoot = @"c:\";
}
else
{
path = "/foo/bar";
expectedRoot = "/";
}

Assert.Equal(new[] { expectedRoot, "foo", "bar" }, new DirectoryInfo(path).SplitFullPath());
}

/// <summary>
Expand All @@ -55,7 +75,18 @@ public void UtilitySplitsAbsolutePaths()
[Fact]
public void UtilityResolvesAndSplitsRelativePaths()
{
var components = new DirectoryInfo(@"foo\bar").SplitFullPath().ToList();
string path;

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
path = @"foo\bar";
}
else
{
path = "foo/bar";
}

var components = new DirectoryInfo(path).SplitFullPath().ToList();
Assert.True(components.Count > 2);
Assert.Equal(new[] { "foo", "bar" }, components.Skip(components.Count - 2));
}
Expand All @@ -66,6 +97,11 @@ public void UtilityResolvesAndSplitsRelativePaths()
[Fact]
public void UtilitySplitsUncPaths()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return;
}

Assert.Equal(new[] { @"\\foo\bar", "baz" }, new DirectoryInfo(@"\\foo\bar\baz").SplitFullPath());
}

Expand Down