Skip to content

Commit

Permalink
Cache improvements. (#954)
Browse files Browse the repository at this point in the history
* Cache improvements.

* Also trim windows path separator.

* Improve scheduler.
  • Loading branch information
SebastianStehle committed Dec 12, 2022
1 parent 438e25f commit 16fc031
Show file tree
Hide file tree
Showing 24 changed files with 633 additions and 274 deletions.
2 changes: 2 additions & 0 deletions backend/i18n/frontend_en.json
Expand Up @@ -282,6 +282,8 @@
"common.errorBack": "Back to previous page.",
"common.errorNoPermission": "You do not have the permissions to do this.",
"common.errorNotFound": "Not Found",
"common.errors.chunkLoadingText": "Failed to load necessary javascript files. Very likely a new very version has been deployed. Do you want to reload?",
"common.errors.chunkLoadingTitle": "Failed to load chunk",
"common.event": "Event",
"common.events": "Events",
"common.executed": "Executed",
Expand Down
2 changes: 2 additions & 0 deletions backend/i18n/frontend_it.json
Expand Up @@ -282,6 +282,8 @@
"common.errorBack": "Torna alla pagina precedente.",
"common.errorNoPermission": "Non hai i permessi per questo.",
"common.errorNotFound": "Non trovato",
"common.errors.chunkLoadingText": "Failed to load necessary javascript files. Very likely a new very version has been deployed. Do you want to reload?",
"common.errors.chunkLoadingTitle": "Failed to load chunk",
"common.event": "Evento",
"common.events": "Eventi",
"common.executed": "Eseguito",
Expand Down
2 changes: 2 additions & 0 deletions backend/i18n/frontend_nl.json
Expand Up @@ -282,6 +282,8 @@
"common.errorBack": "Terug naar de vorige pagina.",
"common.errorNoPermission": "Je hebt niet de permissies om dit te doen.",
"common.errorNotFound": "Niet gevonden",
"common.errors.chunkLoadingText": "Failed to load necessary javascript files. Very likely a new very version has been deployed. Do you want to reload?",
"common.errors.chunkLoadingTitle": "Failed to load chunk",
"common.event": "Evenement",
"common.events": "Evenementen",
"common.executed": "Uitgevoerd",
Expand Down
6 changes: 6 additions & 0 deletions backend/i18n/frontend_pt.json
Expand Up @@ -188,12 +188,16 @@
"clients.connectWizard.manuallyTokenHint": "Tokens normalmente expiram após 30 dias, mas você pode solicitar várias tokens.",
"clients.connectWizard.postManDocs": "Comece com o tutorial do Carteiro na [Documentação](https://docs.squidex.io/02-documentation/developer-guides/api-overview/postman).",
"clients.connectWizard.sdk": "Conecte-se à sua App com a SDK",
"clients.connectWizard.sdkDocumentation": "Documentations for the .NET SDK is available: ",
"clients.connectWizard.sdkHelp": "Precisa de outro SDK?",
"clients.connectWizard.sdkHelpLink": "Contacte-nos no Fórum de Apoio",
"clients.connectWizard.sdkHint": "Descarregue um SDK e estabeleça uma ligação a esta aplicação.",
"clients.connectWizard.sdkStep1": "Instale o .NET SDK",
"clients.connectWizard.sdkStep1Download": "O SDK está disponível em [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.sdkStep2": "Criar um gestor de clientes",
"clients.connectWizard.sdkStep3": "Optionally: Install the Service Extensions for the SDK",
"clients.connectWizard.sdkStep3Download": "The SDK Extension is available on [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary.ServiceExtensions/)",
"clients.connectWizard.sdkStep4": "Optionally: Register the client manager and all clients",
"clients.connectWizard.step0Title": "Cliente de configuração",
"clients.connectWizard.step1Title": "Escolha o método de ligação",
"clients.connectWizard.step2Title": "Ligar",
Expand Down Expand Up @@ -278,6 +282,8 @@
"common.errorBack": "De volta à página anterior.",
"common.errorNoPermission": "Não tem as permissões para fazer isto.",
"common.errorNotFound": "Não encontrado",
"common.errors.chunkLoadingText": "Failed to load necessary javascript files. Very likely a new very version has been deployed. Do you want to reload?",
"common.errors.chunkLoadingTitle": "Failed to load chunk",
"common.event": "Evento",
"common.events": "Eventos",
"common.executed": "Executado",
Expand Down
2 changes: 2 additions & 0 deletions backend/i18n/frontend_zh.json
Expand Up @@ -282,6 +282,8 @@
"common.errorBack": "返回上一页。",
"common.errorNoPermission": "您无权执行此操作。",
"common.errorNotFound": "未找到",
"common.errors.chunkLoadingText": "Failed to load necessary javascript files. Very likely a new very version has been deployed. Do you want to reload?",
"common.errors.chunkLoadingTitle": "Failed to load chunk",
"common.event": "事件",
"common.events": "事件",
"common.executed": "已执行",
Expand Down
2 changes: 2 additions & 0 deletions backend/i18n/source/frontend_en.json
Expand Up @@ -282,6 +282,8 @@
"common.errorBack": "Back to previous page.",
"common.errorNoPermission": "You do not have the permissions to do this.",
"common.errorNotFound": "Not Found",
"common.errors.chunkLoadingText": "Failed to load necessary javascript files. Very likely a new very version has been deployed. Do you want to reload?",
"common.errors.chunkLoadingTitle": "Failed to load chunk",
"common.event": "Event",
"common.events": "Events",
"common.executed": "Executed",
Expand Down
Expand Up @@ -36,7 +36,7 @@ public void RegisterLanguageExtensions(CustomFluidParser parser, TemplateOptions
AddAssetFilter(options);
AddAssetTextFilter(options);

parser.RegisterParserTag("asset",
parser.RegisterParserTag("asset",
parser.PrimaryParser.AndSkip(ZeroOrOne(parser.CommaParser)).And(parser.PrimaryParser),
ResolveAsset);
}
Expand Down
Expand Up @@ -34,7 +34,7 @@ public void RegisterLanguageExtensions(CustomFluidParser parser, TemplateOptions
AddReferenceFilter(options);

parser.RegisterParserTag("reference",
parser.PrimaryParser.AndSkip(ZeroOrOne(parser.CommaParser)).And(parser.PrimaryParser),
parser.PrimaryParser.AndSkip(ZeroOrOne(parser.CommaParser)).And(parser.PrimaryParser),
ResolveReference);
}

Expand Down
47 changes: 16 additions & 31 deletions backend/src/Squidex.Infrastructure/Tasks/Scheduler.cs
Expand Up @@ -13,6 +13,8 @@ namespace Squidex.Infrastructure.Tasks;

public sealed class Scheduler
{
private const int SpecialStateStartedOrDone = 1;
private const int SpecialStateCompleted = -1;
private readonly TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
private readonly SemaphoreSlim semaphore;
private List<SchedulerTask>? tasks;
Expand All @@ -30,7 +32,7 @@ public Scheduler(int maxDegreeOfParallelism = 0)

public void Schedule(SchedulerTask task)
{
if (pendingTasks < 0)
if (pendingTasks <= SpecialStateCompleted)
{
// Already completed.
return;
Expand All @@ -39,7 +41,7 @@ public void Schedule(SchedulerTask task)
if (pendingTasks >= 1)
{
// If we already in a tasks we just queue it with the semaphore.
ScheduleTask(task, default).Forget();
ScheduleTasks(new[] { task }, default);
return;
}

Expand All @@ -50,60 +52,43 @@ public void Schedule(SchedulerTask task)
public async ValueTask CompleteAsync(
CancellationToken ct = default)
{
if (tasks == null || tasks.Count == 0)
// Do not allow another completion call.
if (tasks == null || pendingTasks <= SpecialStateCompleted)
{
return;
}

// Use the value to indicate that the task have been started.
pendingTasks = 1;
pendingTasks = SpecialStateStartedOrDone;
try
{
RunTasks(ct).AsTask().Forget();

ScheduleTasks(tasks, ct);
await tcs.Task;
}
finally
{
pendingTasks = -1;
pendingTasks = SpecialStateCompleted;
}
}

private async ValueTask RunTasks(
private void ScheduleTasks(IReadOnlyCollection<SchedulerTask> taskToSchedule,
CancellationToken ct)
{
// If nothing needs to be done, we can just stop here.
if (tasks == null || tasks.Count == 0)
{
tcs.TrySetResult(true);
return;
}
// Increment the pending tasks once, so we avoid issues when the tasks are executed sequentially.
Interlocked.Add(ref pendingTasks, taskToSchedule.Count);

// Quick check to avoid the allocation of the list.
if (tasks.Count == 1)
foreach (var task in taskToSchedule)
{
await ScheduleTask(tasks[0], ct);
return;
ScheduleTask(task, ct).Forget();
}

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);

// Use the semaphore to reduce degree of parallelization.
await semaphore.WaitAsync(ct);
await task(ct);
}
Expand All @@ -115,7 +100,7 @@ public void Schedule(SchedulerTask task)
{
semaphore.Release();

if (Interlocked.Decrement(ref pendingTasks) <= 1)
if (Interlocked.Decrement(ref pendingTasks) <= SpecialStateStartedOrDone)
{
tcs.TrySetResult(true);
}
Expand Down
6 changes: 3 additions & 3 deletions backend/src/Squidex.Shared/Texts.pt.resx
Expand Up @@ -730,6 +730,9 @@
<data name="history.apps.clientUpdated" xml:space="preserve">
<value>cliente {[Id]} actualizado</value>
</data>
<data name="history.apps.common.updated" xml:space="preserve">
<value>updated general settings</value>
</data>
<data name="history.apps.contributoreAssigned" xml:space="preserve">
<value>associado {user:[Contributor]} a {[Role]}</value>
</data>
Expand Down Expand Up @@ -778,9 +781,6 @@
<data name="history.apps.transfered" xml:space="preserve">
<value>actualizada app ao cliente</value>
</data>
<data name="history.apps.updated" xml:space="preserve">
<value>actualizadas configurações gerais</value>
</data>
<data name="history.apps.workflowAdded" xml:space="preserve">
<value>adicionado fluxo de trabalho {[Name]}.</value>
</data>
Expand Down
94 changes: 94 additions & 0 deletions backend/src/Squidex.Web/IgnoreHashFileProvider.cs
@@ -0,0 +1,94 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================

using System.Text.RegularExpressions;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;

namespace Squidex.Web;

public sealed class IgnoreHashFileProvider : IFileProvider
{
private readonly char[] pathSeparators = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar, '\\' };
private readonly Dictionary<string, string> map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
private readonly IFileProvider inner;

public IgnoreHashFileProvider(IFileProvider inner)
{
this.inner = inner;

var regex = new Regex("^(?<Name>[^.]+)\\.[0-9a-f]{4,}\\.(?<Extension>.+)$");

void MapDirectory(string path)
{
foreach (var file in inner.GetDirectoryContents(path))
{
if (file.IsDirectory)
{
MapDirectory(Combine(path, file.Name));
continue;
}

var match = regex.Match(file.Name);

if (match.Success)
{
var nameWithouthHash = $"{match.Groups["Name"].Value}.{match.Groups["Extension"].Value}";

var pathHashed = Combine(path, file.Name);
var pathNormal = Combine(path, nameWithouthHash);

map[pathNormal] = pathHashed;
}
}
}

MapDirectory(string.Empty);
}

public IFileInfo GetFileInfo(string subpath)
{
var file = inner.GetFileInfo(subpath);

if (!file.Exists)
{
subpath = subpath.TrimStart(pathSeparators).Replace('\\', '/');

if (map.TryGetValue(subpath, out var withHash))
{
file = inner.GetFileInfo(withHash);
}
}

return file;
}

public IDirectoryContents GetDirectoryContents(string subpath)
{
return inner.GetDirectoryContents(subpath);
}

public IChangeToken Watch(string filter)
{
return inner.Watch(filter);
}

private static string Combine(string path1, string path2)
{
if (string.IsNullOrWhiteSpace(path1))
{
return path2;
}

if (string.IsNullOrWhiteSpace(path2))
{
return path1;
}

return $"{path1}/{path2}";
}
}
7 changes: 5 additions & 2 deletions backend/src/Squidex/Areas/Frontend/Startup.cs
Expand Up @@ -10,6 +10,7 @@
using Squidex.Areas.Frontend.Middlewares;
using Squidex.Hosting.Web;
using Squidex.Pipeline.Squid;
using Squidex.Web;
using Squidex.Web.Pipeline;

namespace Squidex.Areas.Frontend;
Expand All @@ -26,8 +27,10 @@ public static void UseFrontend(this IApplicationBuilder app)

if (!environment.IsDevelopment())
{
fileProvider = new CompositeFileProvider(fileProvider,
new PhysicalFileProvider(Path.Combine(environment.WebRootPath, "build")));
var buildFolder = new PhysicalFileProvider(Path.Combine(environment.WebRootPath, "build"));
var buildProvider = new IgnoreHashFileProvider(buildFolder);

fileProvider = new CompositeFileProvider(fileProvider, buildFolder, buildProvider);
}

app.Map("/squid.svg", builder =>
Expand Down
Expand Up @@ -5,10 +5,10 @@
}

@functions {
public string ErrorClass(string error)
{
return ViewData.ModelState[error]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid ? "border-danger" : "";
}
public string ErrorClass(string error)
{
return ViewData.ModelState[error]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid ? "border-danger" : "";
}
}

<form asp-controller="Account" asp-action="Consent" asp-route-returnurl="@Model!.ReturnUrl" method="post">
Expand Down

0 comments on commit 16fc031

Please sign in to comment.