mirror of https://github.com/Squidex/squidex.git
Browse Source
* Improve async code. * Improve assets. * Fix tests. * More tests * Fix mapping. * Fix header names. * Fix languages headerpull/1009/head
committed by
GitHub
65 changed files with 1039 additions and 1082 deletions
@ -0,0 +1,18 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents; |
|||
|
|||
public static class ContentExtensions |
|||
{ |
|||
public static Status EditingStatus(this IContentEntity content) |
|||
{ |
|||
return content.NewStatus ?? content.Status; |
|||
} |
|||
} |
|||
@ -1,116 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks.Dataflow; |
|||
|
|||
namespace Squidex.Infrastructure.Tasks; |
|||
|
|||
public sealed class PartitionedActionBlock<TInput> : ITargetBlock<TInput> |
|||
{ |
|||
private readonly ITargetBlock<TInput> distributor; |
|||
private readonly ActionBlock<TInput>[] workers; |
|||
|
|||
public Task Completion |
|||
{ |
|||
get => Task.WhenAll(workers.Select(x => x.Completion)); |
|||
} |
|||
|
|||
public PartitionedActionBlock(Func<TInput, Task> action, Func<TInput, long> partitioner) |
|||
: this(action, partitioner, new ExecutionDataflowBlockOptions()) |
|||
{ |
|||
} |
|||
|
|||
public PartitionedActionBlock(Func<TInput, Task> action, Func<TInput, long> partitioner, ExecutionDataflowBlockOptions dataflowBlockOptions) |
|||
{ |
|||
Guard.NotNull(action); |
|||
Guard.NotNull(partitioner); |
|||
Guard.NotNull(dataflowBlockOptions); |
|||
Guard.GreaterThan(dataflowBlockOptions.MaxDegreeOfParallelism, 1, nameof(dataflowBlockOptions.MaxDegreeOfParallelism)); |
|||
|
|||
workers = new ActionBlock<TInput>[dataflowBlockOptions.MaxDegreeOfParallelism]; |
|||
|
|||
for (var i = 0; i < dataflowBlockOptions.MaxDegreeOfParallelism; i++) |
|||
{ |
|||
workers[i] = new ActionBlock<TInput>(action, new ExecutionDataflowBlockOptions |
|||
{ |
|||
BoundedCapacity = dataflowBlockOptions.BoundedCapacity, |
|||
CancellationToken = dataflowBlockOptions.CancellationToken, |
|||
MaxDegreeOfParallelism = 1, |
|||
MaxMessagesPerTask = 1, |
|||
TaskScheduler = dataflowBlockOptions.TaskScheduler |
|||
}); |
|||
} |
|||
|
|||
distributor = new ActionBlock<TInput>(async input => |
|||
{ |
|||
try |
|||
{ |
|||
var partition = Math.Abs(partitioner(input)) % workers.Length; |
|||
|
|||
await workers[partition].SendAsync(input); |
|||
} |
|||
catch (OperationCanceledException ex) |
|||
{ |
|||
// Dataflow swallows operation cancelled exception.
|
|||
throw new AggregateException(ex); |
|||
} |
|||
}, new ExecutionDataflowBlockOptions |
|||
{ |
|||
BoundedCapacity = 1, |
|||
MaxDegreeOfParallelism = 1, |
|||
MaxMessagesPerTask = 1 |
|||
}); |
|||
|
|||
LinkCompletion(); |
|||
} |
|||
|
|||
public DataflowMessageStatus OfferMessage(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock<TInput>? source, bool consumeToAccept) |
|||
{ |
|||
return distributor.OfferMessage(messageHeader, messageValue, source, consumeToAccept); |
|||
} |
|||
|
|||
public void Complete() |
|||
{ |
|||
distributor.Complete(); |
|||
} |
|||
|
|||
public void Fault(Exception exception) |
|||
{ |
|||
distributor.Fault(exception); |
|||
} |
|||
|
|||
#pragma warning disable RECS0165 // Asynchronous methods should return a Task instead of void
|
|||
private async void LinkCompletion() |
|||
#pragma warning restore RECS0165 // Asynchronous methods should return a Task instead of void
|
|||
{ |
|||
try |
|||
{ |
|||
await distributor.Completion.ConfigureAwait(false); |
|||
} |
|||
#pragma warning disable RECS0022 // A catch clause that catches System.Exception and has an empty body
|
|||
catch |
|||
#pragma warning restore RECS0022 // A catch clause that catches System.Exception and has an empty body
|
|||
{ |
|||
// we do not want to change the stacktrace of the exception.
|
|||
} |
|||
|
|||
if (distributor.Completion.IsFaulted && distributor.Completion.Exception != null) |
|||
{ |
|||
foreach (var worker in workers) |
|||
{ |
|||
((IDataflowBlock)worker).Fault(distributor.Completion.Exception); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
foreach (var worker in workers) |
|||
{ |
|||
worker.Complete(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,109 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Channels; |
|||
|
|||
namespace Squidex.Infrastructure.Tasks; |
|||
|
|||
public sealed class PartitionedScheduler<T> : IAsyncDisposable |
|||
{ |
|||
private readonly Consumer[] consumers; |
|||
private Exception? exception; |
|||
|
|||
private sealed class Consumer |
|||
{ |
|||
private readonly Channel<T> channel; |
|||
private readonly Task worker; |
|||
|
|||
public Consumer(Func<T, CancellationToken, Task> action, int bufferSize, |
|||
CancellationToken ct) |
|||
{ |
|||
channel = Channel.CreateBounded<T>(new BoundedChannelOptions(bufferSize) |
|||
{ |
|||
SingleReader = true, |
|||
SingleWriter = false |
|||
}); |
|||
|
|||
worker = Task.Run(async () => |
|||
{ |
|||
await foreach (var item in channel.Reader.ReadAllAsync(ct)) |
|||
{ |
|||
await action(item, ct); |
|||
} |
|||
}, ct); |
|||
} |
|||
|
|||
public ValueTask ScheduleAsync(T item, |
|||
CancellationToken ct) |
|||
{ |
|||
return channel.Writer.WriteAsync(item, ct); |
|||
} |
|||
|
|||
public Task CompleteAsync() |
|||
{ |
|||
channel.Writer.TryComplete(); |
|||
|
|||
return worker; |
|||
} |
|||
} |
|||
|
|||
public PartitionedScheduler(Func<T, CancellationToken, Task> action, |
|||
int maxWorkers, |
|||
int maxBuffer, |
|||
CancellationToken ct = default) |
|||
{ |
|||
consumers = new Consumer[maxWorkers]; |
|||
|
|||
for (var i = 0; i < maxWorkers; i++) |
|||
{ |
|||
consumers[i] = new Consumer(action, maxBuffer, ct); |
|||
} |
|||
} |
|||
|
|||
public async ValueTask ScheduleAsync(object key, T item, |
|||
CancellationToken ct = default) |
|||
{ |
|||
if (exception != null) |
|||
{ |
|||
throw exception; |
|||
} |
|||
|
|||
var consumerIndex = Math.Abs((key?.GetHashCode() ?? 0) % consumers.Length); |
|||
var consumerInstance = consumers[consumerIndex]; |
|||
|
|||
try |
|||
{ |
|||
await consumerInstance.ScheduleAsync(item, ct); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
exception = ex; |
|||
} |
|||
} |
|||
|
|||
public async Task CompleteAsync() |
|||
{ |
|||
foreach (var consumer in consumers) |
|||
{ |
|||
#pragma warning disable RECS0022 // A catch clause that catches System.Exception and has an empty body
|
|||
try |
|||
{ |
|||
await consumer.CompleteAsync(); |
|||
} |
|||
catch |
|||
{ |
|||
// Ensure we can complete all workers.
|
|||
} |
|||
#pragma warning restore RECS0022 // A catch clause that catches System.Exception and has an empty body
|
|||
} |
|||
} |
|||
|
|||
public async ValueTask DisposeAsync() |
|||
{ |
|||
await CompleteAsync(); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue