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