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