Add exception handling for SerializeProcess with CancellationTokenSource (#211)
* Add exception handling for SerializeProcess with CancellationTokenSource * formatting * add exception test to make sure we handle a server exception * add extra exception and handling to stop * add comment and another test * one last chance for user to cancel * formatting
This commit is contained in:
@@ -16,6 +16,7 @@ public abstract class ChannelSaver<T>
|
||||
private const int MAX_CACHE_WRITE_PARALLELISM = 4;
|
||||
private const int MAX_CACHE_BATCH = 500;
|
||||
|
||||
private readonly List<Exception> _lists = new();
|
||||
private readonly Channel<T> _checkCacheChannel = Channel.CreateBounded<T>(
|
||||
new BoundedChannelOptions(SEND_CAPACITY)
|
||||
{
|
||||
@@ -28,7 +29,7 @@ public abstract class ChannelSaver<T>
|
||||
_ => throw new NotImplementedException("Dropping items not supported.")
|
||||
);
|
||||
|
||||
public Task Start(CancellationToken cancellationToken = default) =>
|
||||
public Task Start(CancellationToken cancellationToken) =>
|
||||
_checkCacheChannel
|
||||
.Reader.BatchBySize(HTTP_SEND_CHUNK_SIZE)
|
||||
.WithTimeout(HTTP_BATCH_TIMEOUT)
|
||||
@@ -42,10 +43,32 @@ public abstract class ChannelSaver<T>
|
||||
.Join()
|
||||
.Batch(MAX_CACHE_BATCH)
|
||||
.WithTimeout(HTTP_BATCH_TIMEOUT)
|
||||
.ReadAllConcurrently(MAX_CACHE_WRITE_PARALLELISM, SaveToCache, cancellationToken);
|
||||
.ReadAllConcurrently(MAX_CACHE_WRITE_PARALLELISM, SaveToCache, cancellationToken)
|
||||
.ContinueWith(
|
||||
t =>
|
||||
{
|
||||
Exception? ex = t.Exception;
|
||||
if (ex is null && t.Status is TaskStatus.Canceled && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
ex = new OperationCanceledException();
|
||||
}
|
||||
|
||||
public ValueTask Save(T item, CancellationToken cancellationToken = default) =>
|
||||
_checkCacheChannel.Writer.WriteAsync(item, cancellationToken);
|
||||
if (ex is not null)
|
||||
{
|
||||
lock (_lists)
|
||||
{
|
||||
_lists.Add(ex);
|
||||
}
|
||||
}
|
||||
_checkCacheChannel.Writer.TryComplete(ex);
|
||||
},
|
||||
CancellationToken.None,
|
||||
TaskContinuationOptions.ExecuteSynchronously,
|
||||
TaskScheduler.Current
|
||||
);
|
||||
|
||||
public async ValueTask Save(T item, CancellationToken cancellationToken) =>
|
||||
await _checkCacheChannel.Writer.WriteAsync(item, cancellationToken).ConfigureAwait(true);
|
||||
|
||||
public async Task<IMemoryOwner<T>> SendToServer(IMemoryOwner<T> batch, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -55,10 +78,30 @@ public abstract class ChannelSaver<T>
|
||||
|
||||
public abstract Task SendToServer(Batch<T> batch, CancellationToken cancellationToken);
|
||||
|
||||
public Task Done()
|
||||
public void DoneTraversing() => _checkCacheChannel.Writer.TryComplete();
|
||||
|
||||
public async Task DoneSaving()
|
||||
{
|
||||
_checkCacheChannel.Writer.Complete();
|
||||
return Task.CompletedTask;
|
||||
await _checkCacheChannel.Reader.Completion.ConfigureAwait(true);
|
||||
lock (_lists)
|
||||
{
|
||||
if (_lists.Count > 0)
|
||||
{
|
||||
var exceptions = new List<Exception>();
|
||||
foreach (var ex in _lists)
|
||||
{
|
||||
if (ex is AggregateException ae)
|
||||
{
|
||||
exceptions.AddRange(ae.Flatten().InnerExceptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
}
|
||||
throw new AggregateException(exceptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void SaveToCache(List<T> item);
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Speckle.Sdk.Serialisation.V2.Send;
|
||||
|
||||
public sealed class PriorityScheduler(ThreadPriority priority, int maximumConcurrencyLevel) : TaskScheduler, IDisposable
|
||||
public sealed class PriorityScheduler(
|
||||
ILogger<PriorityScheduler> logger,
|
||||
ThreadPriority priority,
|
||||
int maximumConcurrencyLevel
|
||||
) : TaskScheduler, IDisposable
|
||||
{
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new();
|
||||
private readonly BlockingCollection<Task> _tasks = new();
|
||||
@@ -47,10 +52,10 @@ public sealed class PriorityScheduler(ThreadPriority priority, int maximumConcur
|
||||
}
|
||||
}
|
||||
#pragma warning disable CA1031
|
||||
catch (Exception)
|
||||
catch (Exception e)
|
||||
#pragma warning restore CA1031
|
||||
{
|
||||
// ignored
|
||||
logger.LogError(e, "{name} had an exception", Thread.CurrentThread.Name);
|
||||
}
|
||||
})
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.InterfaceGenerator;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Dependencies;
|
||||
@@ -31,13 +32,23 @@ public sealed class SerializeProcess(
|
||||
IServerObjectManager serverObjectManager,
|
||||
IBaseChildFinder baseChildFinder,
|
||||
IObjectSerializerFactory objectSerializerFactory,
|
||||
ILoggerFactory loggerFactory,
|
||||
SerializeProcessOptions? options = null
|
||||
) : ChannelSaver<BaseItem>, ISerializeProcess
|
||||
{
|
||||
private readonly PriorityScheduler _highest = new(ThreadPriority.Highest, 2);
|
||||
private readonly PriorityScheduler _belowNormal = new(ThreadPriority.BelowNormal, Environment.ProcessorCount * 2);
|
||||
private readonly PriorityScheduler _highest = new(
|
||||
loggerFactory.CreateLogger<PriorityScheduler>(),
|
||||
ThreadPriority.Highest,
|
||||
2
|
||||
);
|
||||
private readonly PriorityScheduler _belowNormal = new(
|
||||
loggerFactory.CreateLogger<PriorityScheduler>(),
|
||||
ThreadPriority.BelowNormal,
|
||||
Environment.ProcessorCount * 2
|
||||
);
|
||||
|
||||
private readonly SerializeProcessOptions _options = options ?? new(false, false, false, false);
|
||||
private readonly ILogger<SerializeProcess> _logger = loggerFactory.CreateLogger<SerializeProcess>();
|
||||
|
||||
private readonly ConcurrentDictionary<Id, ObjectReference> _objectReferences = new();
|
||||
private readonly Pool<List<(Id, Json, Closures)>> _pool = Pools.CreateListPool<(Id, Json, Closures)>();
|
||||
@@ -71,16 +82,15 @@ public sealed class SerializeProcess(
|
||||
{
|
||||
findTotalObjectsTask = Task.Factory.StartNew(
|
||||
() => TraverseTotal(root),
|
||||
default,
|
||||
cancellationToken,
|
||||
TaskCreationOptions.AttachedToParent | TaskCreationOptions.PreferFairness,
|
||||
_highest
|
||||
);
|
||||
}
|
||||
|
||||
await Traverse(root, cancellationToken).ConfigureAwait(false);
|
||||
await Done().ConfigureAwait(true);
|
||||
await channelTask.ConfigureAwait(false);
|
||||
await findTotalObjectsTask.ConfigureAwait(false);
|
||||
await Traverse(root, cancellationToken).ConfigureAwait(true);
|
||||
DoneTraversing();
|
||||
await Task.WhenAll(findTotalObjectsTask, channelTask).ConfigureAwait(true);
|
||||
await DoneSaving().ConfigureAwait(true);
|
||||
return new(root.id.NotNull(), _objectReferences.Freeze());
|
||||
}
|
||||
|
||||
@@ -103,7 +113,10 @@ public sealed class SerializeProcess(
|
||||
var tmp = child;
|
||||
var t = Task
|
||||
.Factory.StartNew(
|
||||
() => Traverse(tmp, cancellationToken),
|
||||
async () =>
|
||||
{
|
||||
return await Traverse(tmp, cancellationToken).ConfigureAwait(true);
|
||||
},
|
||||
cancellationToken,
|
||||
TaskCreationOptions.AttachedToParent | TaskCreationOptions.PreferFairness,
|
||||
_belowNormal
|
||||
@@ -204,29 +217,50 @@ public sealed class SerializeProcess(
|
||||
|
||||
public override async Task SendToServer(Batch<BaseItem> batch, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_options.SkipServer && batch.Items.Count != 0)
|
||||
try
|
||||
{
|
||||
var objectBatch = batch.Items.Distinct().ToList();
|
||||
var hasObjects = await serverObjectManager
|
||||
.HasObjects(objectBatch.Select(x => x.Id.Value).Freeze(), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
objectBatch = batch.Items.Where(x => !hasObjects[x.Id.Value]).ToList();
|
||||
if (objectBatch.Count != 0)
|
||||
if (!_options.SkipServer && batch.Items.Count != 0)
|
||||
{
|
||||
await serverObjectManager.UploadObjects(objectBatch, true, progress, cancellationToken).ConfigureAwait(false);
|
||||
Interlocked.Exchange(ref _uploaded, _uploaded + batch.Items.Count);
|
||||
var objectBatch = batch.Items.Distinct().ToList();
|
||||
var hasObjects = await serverObjectManager
|
||||
.HasObjects(objectBatch.Select(x => x.Id.Value).Freeze(), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
objectBatch = batch.Items.Where(x => !hasObjects[x.Id.Value]).ToList();
|
||||
if (objectBatch.Count != 0)
|
||||
{
|
||||
await serverObjectManager.UploadObjects(objectBatch, true, progress, cancellationToken).ConfigureAwait(false);
|
||||
Interlocked.Exchange(ref _uploaded, _uploaded + batch.Items.Count);
|
||||
}
|
||||
|
||||
progress?.Report(new(ProgressEvent.UploadedObjects, _uploaded, null));
|
||||
}
|
||||
progress?.Report(new(ProgressEvent.UploadedObjects, _uploaded, null));
|
||||
}
|
||||
#pragma warning disable CA1031
|
||||
catch (Exception e)
|
||||
#pragma warning restore CA1031
|
||||
{
|
||||
_logger.LogError(e, "Error sending objects to server");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public override void SaveToCache(List<BaseItem> batch)
|
||||
{
|
||||
if (!_options.SkipCacheWrite && batch.Count != 0)
|
||||
try
|
||||
{
|
||||
sqLiteJsonCacheManager.SaveObjects(batch.Select(x => (x.Id.Value, x.Json.Value)));
|
||||
Interlocked.Exchange(ref _cached, _cached + batch.Count);
|
||||
progress?.Report(new(ProgressEvent.CachedToLocal, _cached, _objectsSerialized));
|
||||
if (!_options.SkipCacheWrite && batch.Count != 0)
|
||||
{
|
||||
sqLiteJsonCacheManager.SaveObjects(batch.Select(x => (x.Id.Value, x.Json.Value)));
|
||||
Interlocked.Exchange(ref _cached, _cached + batch.Count);
|
||||
progress?.Report(new(ProgressEvent.CachedToLocal, _cached, _objectsSerialized));
|
||||
}
|
||||
}
|
||||
#pragma warning disable CA1031
|
||||
catch (Exception e)
|
||||
#pragma warning restore CA1031
|
||||
{
|
||||
_logger.LogError(e, "Error sending objects to server");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.Sdk.Serialisation.V2.Receive;
|
||||
using Speckle.Sdk.Serialisation.V2.Send;
|
||||
using Speckle.Sdk.SQLite;
|
||||
@@ -28,7 +29,8 @@ public class SerializeProcessFactory(
|
||||
IObjectSerializerFactory objectSerializerFactory,
|
||||
IObjectDeserializerFactory objectDeserializerFactory,
|
||||
ISqLiteJsonCacheManagerFactory sqLiteJsonCacheManagerFactory,
|
||||
IServerObjectManagerFactory serverObjectManagerFactory
|
||||
IServerObjectManagerFactory serverObjectManagerFactory,
|
||||
ILoggerFactory loggerFactory
|
||||
) : ISerializeProcessFactory
|
||||
{
|
||||
public ISerializeProcess CreateSerializeProcess(
|
||||
@@ -47,6 +49,7 @@ public class SerializeProcessFactory(
|
||||
serverObjectManager,
|
||||
baseChildFinder,
|
||||
objectSerializerFactory,
|
||||
loggerFactory,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma warning disable CA1506
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Speckle.Sdk;
|
||||
using Speckle.Sdk.Credentials;
|
||||
using Speckle.Sdk.Host;
|
||||
@@ -45,7 +46,8 @@ var factory = new SerializeProcessFactory(
|
||||
new ObjectSerializerFactory(new BasePropertyGatherer()),
|
||||
new ObjectDeserializerFactory(),
|
||||
serviceProvider.GetRequiredService<ISqLiteJsonCacheManagerFactory>(),
|
||||
serviceProvider.GetRequiredService<IServerObjectManagerFactory>()
|
||||
serviceProvider.GetRequiredService<IServerObjectManagerFactory>(),
|
||||
new NullLoggerFactory()
|
||||
);
|
||||
var process = factory.CreateDeserializeProcess(new Uri(url), streamId, token, progress, new(skipCacheReceive));
|
||||
var @base = await process.Deserialize(rootId, default).ConfigureAwait(false);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Speckle.Newtonsoft.Json.Linq;
|
||||
using Speckle.Objects.Geometry;
|
||||
using Speckle.Sdk.Host;
|
||||
@@ -37,6 +38,7 @@ public class DetachedTests
|
||||
new DummyServerObjectManager(),
|
||||
new BaseChildFinder(new BasePropertyGatherer()),
|
||||
new ObjectSerializerFactory(new BasePropertyGatherer()),
|
||||
new NullLoggerFactory(),
|
||||
new SerializeProcessOptions(false, false, true, true)
|
||||
);
|
||||
await process2.Serialize(@base, default);
|
||||
@@ -120,6 +122,7 @@ public class DetachedTests
|
||||
new DummyServerObjectManager(),
|
||||
new BaseChildFinder(new BasePropertyGatherer()),
|
||||
new ObjectSerializerFactory(new BasePropertyGatherer()),
|
||||
new NullLoggerFactory(),
|
||||
new SerializeProcessOptions(false, false, true, true)
|
||||
);
|
||||
var results = await process2.Serialize(@base, default);
|
||||
@@ -188,6 +191,7 @@ public class DetachedTests
|
||||
new DummyServerObjectManager(),
|
||||
new BaseChildFinder(new BasePropertyGatherer()),
|
||||
new ObjectSerializerFactory(new BasePropertyGatherer()),
|
||||
new NullLoggerFactory(),
|
||||
new SerializeProcessOptions(false, false, true, true)
|
||||
);
|
||||
var results = await process2.Serialize(@base, default);
|
||||
@@ -221,6 +225,7 @@ public class DetachedTests
|
||||
new DummyServerObjectManager(),
|
||||
new BaseChildFinder(new BasePropertyGatherer()),
|
||||
new ObjectSerializerFactory(new BasePropertyGatherer()),
|
||||
new NullLoggerFactory(),
|
||||
new SerializeProcessOptions(false, false, true, true)
|
||||
);
|
||||
var results = await process2.Serialize(@base, default);
|
||||
@@ -300,7 +305,7 @@ public class DummyServerObjectManager : IServerObjectManager
|
||||
public Task<Dictionary<string, bool>> HasObjects(
|
||||
IReadOnlyCollection<string> objectIds,
|
||||
CancellationToken cancellationToken
|
||||
) => throw new NotImplementedException();
|
||||
) => Task.FromResult(objectIds.ToDictionary(x => x, _ => false));
|
||||
|
||||
public Task UploadObjects(
|
||||
IReadOnlyList<BaseItem> objects,
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"Type": "AggregateException",
|
||||
"InnerException": {
|
||||
"Data": {},
|
||||
"Message": "The method or operation is not implemented.",
|
||||
"StackTrace": "at Speckle.Sdk.Serialization.Tests.ExceptionSendCacheManager.SaveObjects(IEnumerable`1 items)\nat Speckle.Sdk.Serialisation.V2.Send.SerializeProcess.SaveToCache(List`1 batch)\nat Open.ChannelExtensions.<28d3e838-dde6-44a1-8f5e-d1c739a178d0>Extensions.<>c__DisplayClass98_0`1.<ReadAllConcurrently>b__0(T e)\nat Open.ChannelExtensions.<28d3e838-dde6-44a1-8f5e-d1c739a178d0>Extensions.<>c__DisplayClass92_0`1.<ReadAllConcurrentlyAsync>b__2(T item, Int64 _)\nat Open.ChannelExtensions.<28d3e838-dde6-44a1-8f5e-d1c739a178d0>Extensions.ReadUntilCancelledAsync[T](<5e816acc-9cf8-4b52-a8da-ceb5bd7eecc7>ChannelReader`1 reader, CancellationToken cancellationToken, Func`3 receiver, Boolean deferredExecution)\n--- End of stack trace from previous location ---",
|
||||
"Type": "NotImplementedException"
|
||||
},
|
||||
"StackTrace": "at Speckle.Sdk.Dependencies.Serialization.ChannelSaver`1.DoneSaving()\nat Speckle.Sdk.Serialisation.V2.Send.SerializeProcess.Serialize(Base root, CancellationToken cancellationToken)\n--- End of stack trace from previous location ---\nat Xunit.Assert.RecordExceptionAsync(Func`1 testCode)"
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Type": "AggregateException",
|
||||
"InnerExceptions": [
|
||||
{
|
||||
"Data": {},
|
||||
"Message": "The method or operation is not implemented.",
|
||||
"StackTrace": "at Speckle.Sdk.Serialization.Tests.ExceptionServerObjectManager.HasObjects(IReadOnlyCollection`1 objectIds, CancellationToken cancellationToken)\nat Speckle.Sdk.Serialisation.V2.Send.SerializeProcess.SendToServer(Batch`1 batch, CancellationToken cancellationToken)\nat Speckle.Sdk.Dependencies.Serialization.ChannelSaver`1.SendToServer(IMemoryOwner`1 batch, CancellationToken cancellationToken)\n--- End of stack trace from previous location ---\n--- End of stack trace from previous location ---\nat Open.ChannelExtensions.<28d3e838-dde6-44a1-8f5e-d1c739a178d0>Extensions.ReadUntilCancelledAsync[T](<5e816acc-9cf8-4b52-a8da-ceb5bd7eecc7>ChannelReader`1 reader, CancellationToken cancellationToken, Func`3 receiver, Boolean deferredExecution)\n--- End of stack trace from previous location ---",
|
||||
"Type": "NotImplementedException"
|
||||
},
|
||||
{
|
||||
"Data": {},
|
||||
"Message": "The method or operation is not implemented.",
|
||||
"StackTrace": "at Speckle.Sdk.Serialization.Tests.ExceptionServerObjectManager.HasObjects(IReadOnlyCollection`1 objectIds, CancellationToken cancellationToken)\nat Speckle.Sdk.Serialisation.V2.Send.SerializeProcess.SendToServer(Batch`1 batch, CancellationToken cancellationToken)\nat Speckle.Sdk.Dependencies.Serialization.ChannelSaver`1.SendToServer(IMemoryOwner`1 batch, CancellationToken cancellationToken)\n--- End of stack trace from previous location ---\n--- End of stack trace from previous location ---\nat Open.ChannelExtensions.<28d3e838-dde6-44a1-8f5e-d1c739a178d0>Extensions.ReadUntilCancelledAsync[T](<5e816acc-9cf8-4b52-a8da-ceb5bd7eecc7>ChannelReader`1 reader, CancellationToken cancellationToken, Func`3 receiver, Boolean deferredExecution)\n--- End of stack trace from previous location ---",
|
||||
"Type": "NotImplementedException"
|
||||
},
|
||||
{
|
||||
"Data": {},
|
||||
"Message": "The method or operation is not implemented.",
|
||||
"StackTrace": "at Speckle.Sdk.Serialization.Tests.ExceptionServerObjectManager.HasObjects(IReadOnlyCollection`1 objectIds, CancellationToken cancellationToken)\nat Speckle.Sdk.Serialisation.V2.Send.SerializeProcess.SendToServer(Batch`1 batch, CancellationToken cancellationToken)\nat Speckle.Sdk.Dependencies.Serialization.ChannelSaver`1.SendToServer(IMemoryOwner`1 batch, CancellationToken cancellationToken)\n--- End of stack trace from previous location ---\n--- End of stack trace from previous location ---\nat Open.ChannelExtensions.<28d3e838-dde6-44a1-8f5e-d1c739a178d0>Extensions.ReadUntilCancelledAsync[T](<5e816acc-9cf8-4b52-a8da-ceb5bd7eecc7>ChannelReader`1 reader, CancellationToken cancellationToken, Func`3 receiver, Boolean deferredExecution)\n--- End of stack trace from previous location ---",
|
||||
"Type": "NotImplementedException"
|
||||
},
|
||||
{
|
||||
"Data": {},
|
||||
"Message": "The method or operation is not implemented.",
|
||||
"StackTrace": "at Speckle.Sdk.Serialization.Tests.ExceptionServerObjectManager.HasObjects(IReadOnlyCollection`1 objectIds, CancellationToken cancellationToken)\nat Speckle.Sdk.Serialisation.V2.Send.SerializeProcess.SendToServer(Batch`1 batch, CancellationToken cancellationToken)\nat Speckle.Sdk.Dependencies.Serialization.ChannelSaver`1.SendToServer(IMemoryOwner`1 batch, CancellationToken cancellationToken)\n--- End of stack trace from previous location ---\n--- End of stack trace from previous location ---\nat Open.ChannelExtensions.<28d3e838-dde6-44a1-8f5e-d1c739a178d0>Extensions.ReadUntilCancelledAsync[T](<5e816acc-9cf8-4b52-a8da-ceb5bd7eecc7>ChannelReader`1 reader, CancellationToken cancellationToken, Func`3 receiver, Boolean deferredExecution)\n--- End of stack trace from previous location ---",
|
||||
"Type": "NotImplementedException"
|
||||
}
|
||||
],
|
||||
"StackTrace": "at Speckle.Sdk.Dependencies.Serialization.ChannelSaver`1.DoneSaving()\nat Speckle.Sdk.Serialisation.V2.Send.SerializeProcess.Serialize(Base root, CancellationToken cancellationToken)\n--- End of stack trace from previous location ---\nat Xunit.Assert.RecordExceptionAsync(Func`1 testCode)"
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Speckle.Objects.Geometry;
|
||||
using Speckle.Sdk.Host;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Serialisation.V2;
|
||||
using Speckle.Sdk.Serialisation.V2.Send;
|
||||
using Speckle.Sdk.SQLite;
|
||||
using Speckle.Sdk.Transports;
|
||||
|
||||
namespace Speckle.Sdk.Serialization.Tests;
|
||||
|
||||
public class ExceptionTests
|
||||
{
|
||||
public ExceptionTests()
|
||||
{
|
||||
TypeLoader.Reset();
|
||||
TypeLoader.Initialize(typeof(Base).Assembly, typeof(DetachedTests).Assembly, typeof(Polyline).Assembly);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Test_Exceptions_Upload()
|
||||
{
|
||||
var testClass = new TestClass() { RegularProperty = "Hello" };
|
||||
|
||||
var objects = new Dictionary<string, string>();
|
||||
using var process2 = new SerializeProcess(
|
||||
null,
|
||||
new DummySendCacheManager(objects),
|
||||
new ExceptionServerObjectManager(),
|
||||
new BaseChildFinder(new BasePropertyGatherer()),
|
||||
new ObjectSerializerFactory(new BasePropertyGatherer()),
|
||||
new NullLoggerFactory(),
|
||||
new SerializeProcessOptions(false, false, false, true)
|
||||
);
|
||||
|
||||
//4 exceptions are fine because we use 4 threads for saving cache
|
||||
var ex = await Assert.ThrowsAsync<AggregateException>(async () => await process2.Serialize(testClass, default));
|
||||
await Verify(ex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Test_Exceptions_Cache()
|
||||
{
|
||||
var testClass = new TestClass() { RegularProperty = "Hello" };
|
||||
|
||||
using var process2 = new SerializeProcess(
|
||||
null,
|
||||
new ExceptionSendCacheManager(),
|
||||
new DummyServerObjectManager(),
|
||||
new BaseChildFinder(new BasePropertyGatherer()),
|
||||
new ObjectSerializerFactory(new BasePropertyGatherer()),
|
||||
new NullLoggerFactory(),
|
||||
new SerializeProcessOptions(false, false, false, true)
|
||||
);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<AggregateException>(async () => await process2.Serialize(testClass, default));
|
||||
await Verify(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public class ExceptionServerObjectManager : IServerObjectManager
|
||||
{
|
||||
public IAsyncEnumerable<(string, string)> DownloadObjects(
|
||||
IReadOnlyCollection<string> objectIds,
|
||||
IProgress<ProgressArgs>? progress,
|
||||
CancellationToken cancellationToken
|
||||
) => throw new NotImplementedException();
|
||||
|
||||
public Task<string?> DownloadSingleObject(
|
||||
string objectId,
|
||||
IProgress<ProgressArgs>? progress,
|
||||
CancellationToken cancellationToken
|
||||
) => throw new NotImplementedException();
|
||||
|
||||
public Task<Dictionary<string, bool>> HasObjects(
|
||||
IReadOnlyCollection<string> objectIds,
|
||||
CancellationToken cancellationToken
|
||||
) => throw new NotImplementedException();
|
||||
|
||||
public Task UploadObjects(
|
||||
IReadOnlyList<BaseItem> objects,
|
||||
bool compressPayloads,
|
||||
IProgress<ProgressArgs>? progress,
|
||||
CancellationToken cancellationToken
|
||||
) => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public class ExceptionSendCacheManager : ISqLiteJsonCacheManager
|
||||
{
|
||||
public void Dispose() { }
|
||||
|
||||
public IReadOnlyCollection<(string Id, string Json)> GetAllObjects() => throw new NotImplementedException();
|
||||
|
||||
public void DeleteObject(string id) => throw new NotImplementedException();
|
||||
|
||||
public string? GetObject(string id) => null;
|
||||
|
||||
public void SaveObject(string id, string json) => throw new NotImplementedException();
|
||||
|
||||
public void UpdateObject(string id, string json) => throw new NotImplementedException();
|
||||
|
||||
public void SaveObjects(IEnumerable<(string id, string json)> items) => throw new NotImplementedException();
|
||||
|
||||
public bool HasObject(string objectId) => throw new NotImplementedException();
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Speckle.Sdk.Host;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Speckle.Sdk.Host;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Serialisation.V2.Send;
|
||||
|
||||
@@ -24,6 +25,7 @@ public class ExplicitInterfaceTests
|
||||
new DummyServerObjectManager(),
|
||||
new BaseChildFinder(new BasePropertyGatherer()),
|
||||
new ObjectSerializerFactory(new BasePropertyGatherer()),
|
||||
new NullLoggerFactory(),
|
||||
new SerializeProcessOptions(false, false, true, true)
|
||||
);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections.Concurrent;
|
||||
using System.IO.Compression;
|
||||
using System.Reflection;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Newtonsoft.Json.Linq;
|
||||
using Speckle.Objects.Data;
|
||||
@@ -265,6 +266,7 @@ public class SerializationTests
|
||||
new DummySendServerObjectManager(newIdToJson),
|
||||
new BaseChildFinder(new BasePropertyGatherer()),
|
||||
new ObjectSerializerFactory(new BasePropertyGatherer()),
|
||||
new NullLoggerFactory(),
|
||||
new SerializeProcessOptions(true, true, false, true)
|
||||
);
|
||||
var (rootId2, _) = await serializeProcess.Serialize(root, default);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<PackageReference Include="altcover" />
|
||||
<PackageReference Include="FluentAssertions" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="xunit.assert" />
|
||||
<PackageReference Include="xunit.runner.visualstudio"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -55,6 +55,12 @@
|
||||
"resolved": "0.9.6",
|
||||
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
|
||||
},
|
||||
"xunit.assert": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.9.3, )",
|
||||
"resolved": "2.9.3",
|
||||
"contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA=="
|
||||
},
|
||||
"xunit.runner.visualstudio": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.0.1, )",
|
||||
|
||||
Reference in New Issue
Block a user