mirror of https://github.com/Squidex/squidex.git
Browse Source
* Json improvements. * Performance improvements. * Fixes json handling performance. * Telemetry improvements. * Add missing files.pull/892/head
committed by
GitHub
104 changed files with 1302 additions and 1018 deletions
@ -0,0 +1,79 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Concurrent; |
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Json; |
|||
using Squidex.Infrastructure.Tasks; |
|||
using Squidex.Infrastructure.Validation; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.ValidateContent |
|||
{ |
|||
public sealed class RootContext |
|||
{ |
|||
private readonly ConcurrentBag<ValidationError> errors = new ConcurrentBag<ValidationError>(); |
|||
private readonly Scheduler scheduler = new Scheduler(); |
|||
|
|||
public IJsonSerializer JsonSerializer { get; } |
|||
|
|||
public DomainId ContentId { get; } |
|||
|
|||
public NamedId<DomainId> AppId { get; } |
|||
|
|||
public NamedId<DomainId> SchemaId { get; } |
|||
|
|||
public Schema Schema { get; } |
|||
|
|||
public ResolvedComponents Components { get; } |
|||
|
|||
public IEnumerable<ValidationError> Errors |
|||
{ |
|||
get => errors; |
|||
} |
|||
|
|||
public RootContext( |
|||
IJsonSerializer jsonSerializer, |
|||
NamedId<DomainId> appId, |
|||
NamedId<DomainId> schemaId, |
|||
Schema schema, |
|||
DomainId contentId, |
|||
ResolvedComponents components) |
|||
{ |
|||
AppId = appId; |
|||
Components = components; |
|||
ContentId = contentId; |
|||
JsonSerializer = jsonSerializer; |
|||
Schema = schema; |
|||
SchemaId = schemaId; |
|||
} |
|||
|
|||
public void AddError(IEnumerable<string> path, string message) |
|||
{ |
|||
errors.Add(new ValidationError(message, path.ToPathString())); |
|||
} |
|||
|
|||
public void AddTask(SchedulerTask task) |
|||
{ |
|||
scheduler.Schedule(task); |
|||
} |
|||
|
|||
public void ThrowOnErrors() |
|||
{ |
|||
if (!errors.IsEmpty) |
|||
{ |
|||
throw new ValidationException(errors.ToList()); |
|||
} |
|||
} |
|||
|
|||
public ValueTask CompleteAsync( |
|||
CancellationToken ct = default) |
|||
{ |
|||
return scheduler.CompleteAsync(ct); |
|||
} |
|||
} |
|||
} |
|||
@ -1,36 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.ValidateContent |
|||
{ |
|||
public abstract class ValidatorContext |
|||
{ |
|||
public NamedId<DomainId> AppId { get; } |
|||
|
|||
public NamedId<DomainId> SchemaId { get; } |
|||
|
|||
public Schema Schema { get; } |
|||
|
|||
public ValidationMode Mode { get; protected set; } |
|||
|
|||
public ValidationAction Action { get; protected set; } |
|||
|
|||
protected ValidatorContext( |
|||
NamedId<DomainId> appId, |
|||
NamedId<DomainId> schemaId, |
|||
Schema schema) |
|||
{ |
|||
AppId = appId; |
|||
|
|||
Schema = schema; |
|||
SchemaId = schemaId; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,118 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Diagnostics; |
|||
using Orleans; |
|||
using Orleans.Runtime; |
|||
|
|||
#pragma warning disable MA0048 // File name must match type name
|
|||
|
|||
namespace Squidex.Infrastructure.Orleans |
|||
{ |
|||
public abstract class ActivityPropagationGrainCallFilter |
|||
{ |
|||
public const string ActivityNameIn = "Orleans.Runtime.GrainCall.In"; |
|||
public const string ActivityNameOut = "Orleans.Runtime.GrainCall.Out"; |
|||
protected const string TraceParentHeaderName = "traceparent"; |
|||
protected const string TraceStateHeaderName = "tracestate"; |
|||
|
|||
protected static async Task ProcessNewActivity(IGrainCallContext context, string activityName, ActivityKind activityKind, ActivityContext activityContext) |
|||
{ |
|||
ActivityTagsCollection? tags = null; |
|||
|
|||
if (Telemetry.Activities.HasListeners()) |
|||
{ |
|||
tags = new ActivityTagsCollection |
|||
{ |
|||
{ "net.peer.name", context.Grain?.ToString() }, |
|||
{ "rpc.method", context.InterfaceMethod?.Name }, |
|||
{ "rpc.service", context.InterfaceMethod?.DeclaringType?.FullName }, |
|||
{ "rpc.system", "orleans" } |
|||
}; |
|||
} |
|||
|
|||
using (var activity = Telemetry.Activities.StartActivity(activityName, activityKind, activityContext, tags)) |
|||
{ |
|||
if (activity is not null) |
|||
{ |
|||
RequestContext.Set(TraceParentHeaderName, activity.Id); |
|||
} |
|||
|
|||
try |
|||
{ |
|||
await context.Invoke(); |
|||
|
|||
if (activity is not null && activity.IsAllDataRequested) |
|||
{ |
|||
activity.SetTag("status", "Ok"); |
|||
} |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
if (activity is not null && activity.IsAllDataRequested) |
|||
{ |
|||
// Exception attributes from https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/exceptions.md
|
|||
activity.SetTag("exception.type", e.GetType().FullName); |
|||
activity.SetTag("exception.message", e.Message); |
|||
activity.SetTag("exception.stacktrace", e.StackTrace); |
|||
activity.SetTag("exception.escaped", true); |
|||
activity.SetTag("status", "Error"); |
|||
} |
|||
|
|||
throw; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public sealed class ActivityPropagationOutgoingGrainCallFilter : ActivityPropagationGrainCallFilter, IOutgoingGrainCallFilter |
|||
{ |
|||
public Task Invoke(IOutgoingGrainCallContext context) |
|||
{ |
|||
if (Activity.Current != null) |
|||
{ |
|||
return ProcessCurrentActivity(context); |
|||
} |
|||
|
|||
return ProcessNewActivity(context, ActivityNameOut, ActivityKind.Client, default); |
|||
} |
|||
|
|||
private static Task ProcessCurrentActivity(IOutgoingGrainCallContext context) |
|||
{ |
|||
var currentActivity = Activity.Current; |
|||
|
|||
if (currentActivity is not null && currentActivity.IdFormat == ActivityIdFormat.W3C) |
|||
{ |
|||
RequestContext.Set(TraceParentHeaderName, currentActivity.Id); |
|||
|
|||
if (currentActivity.TraceStateString is not null) |
|||
{ |
|||
RequestContext.Set(TraceStateHeaderName, currentActivity.TraceStateString); |
|||
} |
|||
} |
|||
|
|||
return context.Invoke(); |
|||
} |
|||
} |
|||
|
|||
public sealed class ActivityPropagationIncomingGrainCallFilter : ActivityPropagationGrainCallFilter, IIncomingGrainCallFilter |
|||
{ |
|||
public Task Invoke(IIncomingGrainCallContext context) |
|||
{ |
|||
var traceParent = RequestContext.Get(TraceParentHeaderName) as string; |
|||
var traceState = RequestContext.Get(TraceStateHeaderName) as string; |
|||
var parentContext = default(ActivityContext); |
|||
|
|||
if (traceParent is not null) |
|||
{ |
|||
parentContext = ActivityContext.Parse(traceParent, traceState); |
|||
} |
|||
|
|||
return ProcessNewActivity(context, ActivityNameIn, ActivityKind.Server, parentContext); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,123 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure.Tasks |
|||
{ |
|||
#pragma warning disable MA0048 // File name must match type name
|
|||
public delegate Task SchedulerTask(CancellationToken ct); |
|||
#pragma warning restore MA0048 // File name must match type name
|
|||
|
|||
public sealed class Scheduler |
|||
{ |
|||
private readonly TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(); |
|||
private readonly SemaphoreSlim semaphore; |
|||
private List<SchedulerTask>? tasks; |
|||
private int pendingTasks; |
|||
|
|||
public Scheduler(int maxDegreeOfParallelism = 0) |
|||
{ |
|||
if (maxDegreeOfParallelism <= 0) |
|||
{ |
|||
maxDegreeOfParallelism = Environment.ProcessorCount * 2; |
|||
} |
|||
|
|||
semaphore = new SemaphoreSlim(maxDegreeOfParallelism); |
|||
} |
|||
|
|||
public void Schedule(SchedulerTask task) |
|||
{ |
|||
if (pendingTasks < 0) |
|||
{ |
|||
// Already completed.
|
|||
return; |
|||
} |
|||
|
|||
if (pendingTasks >= 1) |
|||
{ |
|||
// If we already in a tasks we just queue it with the semaphore.
|
|||
ScheduleTask(task, default).Forget(); |
|||
return; |
|||
} |
|||
|
|||
tasks ??= new List<SchedulerTask>(1); |
|||
tasks.Add(task); |
|||
} |
|||
|
|||
public async ValueTask CompleteAsync( |
|||
CancellationToken ct = default) |
|||
{ |
|||
if (tasks == null || tasks.Count == 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// Use the value to indicate that the task have been started.
|
|||
pendingTasks = 1; |
|||
try |
|||
{ |
|||
RunTasks(ct).AsTask().Forget(); |
|||
|
|||
await tcs.Task; |
|||
} |
|||
finally |
|||
{ |
|||
pendingTasks = -1; |
|||
} |
|||
} |
|||
|
|||
private async ValueTask RunTasks( |
|||
CancellationToken ct) |
|||
{ |
|||
// If nothing needs to be done, we can just stop here.
|
|||
if (tasks == null || tasks.Count == 0) |
|||
{ |
|||
tcs.TrySetResult(true); |
|||
return; |
|||
} |
|||
|
|||
// Quick check to avoid the allocation of the list.
|
|||
if (tasks.Count == 1) |
|||
{ |
|||
await ScheduleTask(tasks[0], ct); |
|||
return; |
|||
} |
|||
|
|||
var runningTasks = new List<Task>(); |
|||
|
|||
foreach (var validationTask in tasks) |
|||
{ |
|||
runningTasks.Add(ScheduleTask(validationTask, ct)); |
|||
} |
|||
|
|||
await Task.WhenAll(runningTasks); |
|||
} |
|||
|
|||
private async Task ScheduleTask(SchedulerTask task, |
|||
CancellationToken ct) |
|||
{ |
|||
try |
|||
{ |
|||
// Use the interlock to reduce degree of parallelization.
|
|||
Interlocked.Increment(ref pendingTasks); |
|||
|
|||
await semaphore.WaitAsync(ct); |
|||
await task(ct); |
|||
} |
|||
catch |
|||
{ |
|||
return; |
|||
} |
|||
finally |
|||
{ |
|||
if (Interlocked.Decrement(ref pendingTasks) <= 1) |
|||
{ |
|||
tcs.TrySetResult(true); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue