From 76e2716b730140958f6d7526fb97818be40798a1 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 6 Dec 2022 16:58:02 +0100 Subject: [PATCH] Improve timeout for query parsing. --- .../Assets/Queries/AssetQueryService.cs | 14 ++++++++++- .../Contents/Queries/ContentQueryParser.cs | 23 +++++++++++-------- .../Contents/Queries/ContentQueryService.cs | 16 +++++++++++-- .../Contents/Queries/GeoQueryTransformer.cs | 9 ++++---- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs index 8bf5e860f..a394d0baa 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs @@ -177,7 +177,7 @@ public sealed class AssetQueryService : IAssetQueryService using (Telemetry.Activities.StartActivity("AssetQueryService/QueryAsync")) { - q = await queryParser.ParseAsync(context, q, ct); + q = await ParseCoreAsync(context, q, ct); var assets = await QueryCoreAsync(context, parentId, q, ct); @@ -215,6 +215,18 @@ public sealed class AssetQueryService : IAssetQueryService } } + private async Task ParseCoreAsync(Context context, Q q, + CancellationToken ct) + { + using (var combined = CancellationTokenSource.CreateLinkedTokenSource(ct)) + { + // Enforce a hard timeout + combined.CancelAfter(options.TimeoutQuery); + + return await queryParser.ParseAsync(context, q, ct); + } + } + private async Task> QueryFoldersCoreAsync(Context context, DomainId parentId, CancellationToken ct) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs index 821b2a81f..02ebebffc 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs @@ -30,7 +30,7 @@ public class ContentQueryParser private static readonly TimeSpan CacheTime = TimeSpan.FromMinutes(60); private readonly IMemoryCache cache; private readonly IJsonSerializer serializer; - private readonly IAppProvider appprovider; + private readonly IAppProvider appProvider; private readonly ITextIndex textIndex; private readonly ContentOptions options; @@ -38,22 +38,23 @@ public class ContentQueryParser IMemoryCache cache, IJsonSerializer serializer) { this.serializer = serializer; - this.appprovider = appprovider; + this.appProvider = appprovider; this.textIndex = textIndex; this.cache = cache; this.options = options.Value; } - public virtual async Task ParseAsync(Context context, Q q, ISchemaEntity? schema = null) + public virtual async Task ParseAsync(Context context, Q q, ISchemaEntity? schema = null, + CancellationToken ct = default) { Guard.NotNull(context); Guard.NotNull(q); using (Telemetry.Activities.StartActivity("ContentQueryParser/ParseAsync")) { - var query = await ParseClrQueryAsync(context, q, schema); + var query = await ParseClrQueryAsync(context, q, schema, ct); - await TransformFilterAsync(query, context, schema); + await TransformFilterAsync(query, context, schema, ct); WithSorting(query); WithPaging(query, q); @@ -73,11 +74,12 @@ public class ContentQueryParser } } - private async Task TransformFilterAsync(ClrQuery query, Context context, ISchemaEntity? schema) + private async Task TransformFilterAsync(ClrQuery query, Context context, ISchemaEntity? schema, + CancellationToken ct) { if (query.Filter != null && schema != null) { - query.Filter = await GeoQueryTransformer.TransformAsync(query.Filter, context, schema, textIndex); + query.Filter = await GeoQueryTransformer.TransformAsync(query.Filter, context, schema, textIndex, ct); } if (!string.IsNullOrWhiteSpace(query.FullText)) @@ -93,7 +95,7 @@ public class ContentQueryParser PreferredSchemaId = schema.Id }; - var fullTextIds = await textIndex.SearchAsync(context.App, textQuery, context.Scope()); + var fullTextIds = await textIndex.SearchAsync(context.App, textQuery, context.Scope(), ct); var fullTextFilter = ClrFilter.Eq("id", "__notfound__"); if (fullTextIds?.Any() == true) @@ -114,13 +116,14 @@ public class ContentQueryParser } } - private async Task ParseClrQueryAsync(Context context, Q q, ISchemaEntity? schema) + private async Task ParseClrQueryAsync(Context context, Q q, ISchemaEntity? schema, + CancellationToken ct) { var components = ResolvedComponents.Empty; if (schema != null) { - components = await appprovider.GetComponentsAsync(schema); + components = await appProvider.GetComponentsAsync(schema, ct); } var query = q.Query; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs index 1896a0a7d..fae4b4af0 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs @@ -94,7 +94,7 @@ public sealed class ContentQueryService : IContentQueryService q = q with { CreatedBy = context.UserPrincipal.Token() }; } - q = await queryParser.ParseAsync(context, q, schema); + q = await ParseCoreAsync(context, q, schema, ct); var contents = await QueryCoreAsync(context, q, schema, ct); @@ -126,7 +126,7 @@ public sealed class ContentQueryService : IContentQueryService return ResultList.Empty(); } - q = await queryParser.ParseAsync(context, q); + q = await ParseCoreAsync(context, q, null, ct); var contents = await QueryCoreAsync(context, q, schemas, ct); @@ -215,6 +215,18 @@ public sealed class ContentQueryService : IContentQueryService return schemas.Where(x => IsAccessible(x) && HasPermission(context, x, PermissionIds.AppContentsReadOwn)).ToList(); } + private async Task ParseCoreAsync(Context context, Q q, ISchemaEntity? schema, + CancellationToken ct) + { + using (var combined = CancellationTokenSource.CreateLinkedTokenSource(ct)) + { + // Enforce a hard timeout + combined.CancelAfter(options.TimeoutQuery); + + return await queryParser.ParseAsync(context, q, schema, ct); + } + } + private async Task> QueryCoreAsync(Context context, Q q, ISchemaEntity schema, CancellationToken ct) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/GeoQueryTransformer.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/GeoQueryTransformer.cs index 4719e61a0..a192ccc7c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/GeoQueryTransformer.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/GeoQueryTransformer.cs @@ -17,15 +17,16 @@ internal sealed class GeoQueryTransformer : AsyncTransformVisitor?> TransformAsync(FilterNode filter, Context context, ISchemaEntity schema, ITextIndex textIndex) + public static async Task?> TransformAsync(FilterNode filter, Context context, ISchemaEntity schema, ITextIndex textIndex, + CancellationToken ct) { - var args = new Args(context, schema, textIndex); + var args = new Args(context, schema, textIndex, ct); return await filter.Accept(Instance, args); } @@ -39,7 +40,7 @@ internal sealed class GeoQueryTransformer : AsyncTransformVisitor