Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main-v4'
Browse files Browse the repository at this point in the history
  • Loading branch information
exyi committed Oct 16, 2024
2 parents f73922b + b6cc7e8 commit 59dea87
Show file tree
Hide file tree
Showing 59 changed files with 1,113 additions and 168 deletions.
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- 'main'
- 'main-*'
- 'release/**'
pull_request:
workflow_dispatch:
Expand Down
6 changes: 6 additions & 0 deletions src/Framework/Core/Storage/UploadedFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@ namespace DotVVM.Core.Storage
{
public class UploadedFile
{
/// <summary> A unique, randomly generated ID of the uploaded file. Use this ID to get the file from <see cref="IUploadedFileStorage.GetFileAsync(Guid)" /> </summary>
public Guid FileId { get; set; }

/// <summary> A user-specified name of the file. Use with caution, the user may specify this to be any string (for example <c>../../Web.config</c>). </summary>
public string? FileName { get; set; }

/// <summary> Length of the file in bytes. Use with caution, the user may manipulate with this property and it might not correspond to the file returned from <see cref="IUploadedFileStorage" />. </summary>
public FileSize FileSize { get; set; } = new FileSize();

/// <summary> If the file type matched one of type MIME types or extensions in <c>FileUpload.AllowedFileTypes</c>. Use with caution, the user may manipulate with this property. </summary>
public bool IsFileTypeAllowed { get; set; } = true;

/// <summary> If the file size is larger that the limit specified in <c>FileUpload.MaxFileSize</c>. Use with caution, the user may manipulate with this property. </summary>
public bool IsMaxSizeExceeded { get; set; } = false;

/// <summary> If the file satisfies both allowed file types and the size limit. Use with caution, the user may manipulate with this property. </summary>
public bool IsAllowed
=> IsFileTypeAllowed && !IsMaxSizeExceeded;
}
Expand Down
6 changes: 3 additions & 3 deletions src/Framework/Framework/Binding/BindingHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ public static string FormatKnockoutScript(this ParametrizedCode code, DotvvmBind
/// Gets Internal.PathFragmentProperty or DataContext.KnockoutExpression. Returns null if none of these is set.
/// </summary>
public static string? GetDataContextPathFragment(this DotvvmBindableObject currentControl) =>
(string?)currentControl.GetValue(Internal.PathFragmentProperty, inherit: false) ??
(currentControl.GetBinding(DotvvmBindableObject.DataContextProperty, inherit: false) is IValueBinding binding ?
currentControl.properties.TryGet(Internal.PathFragmentProperty, out var pathFragment) && pathFragment is string pathFragmentStr ? pathFragmentStr :
currentControl.properties.TryGet(DotvvmBindableObject.DataContextProperty, out var dataContext) && dataContext is IValueBinding binding ?
binding.GetProperty<SimplePathExpressionBindingProperty>()
.Code.FormatKnockoutScript(currentControl, binding) :
null);
null;


// PERF: maybe safe last GetValue's target/binding to ThreadLocal variable, so the path does not have to be traversed twice
Expand Down
44 changes: 44 additions & 0 deletions src/Framework/Framework/Binding/DotvvmBindingCacheHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,50 @@ public ValueBindingExpression<TResult> CreateValueBinding<TResult>(string code,
}));
}

/// <summary> Compiles a new `{resource: ...code...}` binding which can be evaluated server-side. The result is cached. <see cref="ResourceBindingExpression.ResourceBindingExpression(BindingCompilationService, IEnumerable{object})" /> </summary>
public ResourceBindingExpression CreateResourceBinding(string code, DataContextStack dataContext, BindingParserOptions? parserOptions = null)
{
return CreateCachedBinding("ResourceBinding:" + code, new object?[] { dataContext, parserOptions }, () =>
new ResourceBindingExpression(compilationService, new object?[] {
dataContext,
new OriginalStringBindingProperty(code),
parserOptions
}));
}

/// <summary> Compiles a new `{resource: ...code...}` binding which can be evaluated server-side. The result is implicitly converted to <typeparamref name="TResult" />. The result is cached. </summary>
public ResourceBindingExpression<TResult> CreateResourceBinding<TResult>(string code, DataContextStack dataContext, BindingParserOptions? parserOptions = null)
{
return CreateCachedBinding($"ResourceBinding<{typeof(TResult).ToCode()}>:{code}", new object?[] { dataContext, parserOptions }, () =>
new ResourceBindingExpression<TResult>(compilationService, new object?[] {
dataContext,
new OriginalStringBindingProperty(code),
parserOptions
}));
}

/// <summary> Compiles a new `{command: ...code...}` binding which can be evaluated server-side and also client-side. The result is cached. Note that command bindings might be easier to create using the <see cref="CommandBindingExpression.CommandBindingExpression(BindingCompilationService, Func{object[], System.Threading.Tasks.Task}, string)" /> constructor. </summary>
public CommandBindingExpression CreateCommand(string code, DataContextStack dataContext, BindingParserOptions? parserOptions = null)
{
return CreateCachedBinding($"Command:{code}", new object?[] { dataContext, parserOptions }, () =>
new CommandBindingExpression(compilationService, new object?[] {
dataContext,
new OriginalStringBindingProperty(code),
parserOptions
}));
}

/// <summary> Compiles a new `{command: ...code...}` binding which can be evaluated server-side and also client-side. The result is implicitly converted to <typeparamref name="TResult" />. The result is cached. Note that command bindings might be easier to create using the <see cref="CommandBindingExpression.CommandBindingExpression(BindingCompilationService, Func{object[], System.Threading.Tasks.Task}, string)" /> constructor. </summary>
public CommandBindingExpression<TResult> CreateCommand<TResult>(string code, DataContextStack dataContext, BindingParserOptions? parserOptions = null)
{
return CreateCachedBinding($"Command<{typeof(TResult).ToCode()}>:{code}", new object?[] { dataContext, parserOptions }, () =>
new CommandBindingExpression<TResult>(compilationService, new object?[] {
dataContext,
new OriginalStringBindingProperty(code),
parserOptions
}));
}

/// <summary> Compiles a new `{staticCommand: ...code...}` binding which can be evaluated server-side and also client-side. The result is cached. </summary>
public StaticCommandBindingExpression CreateStaticCommand(string code, DataContextStack dataContext, BindingParserOptions? parserOptions = null)
{
Expand Down
6 changes: 5 additions & 1 deletion src/Framework/Framework/Binding/DotvvmProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@ internal void AddUsedInCapability(DotvvmCapabilityProperty? p)
if (p is object)
lock(this)
{
UsedInCapabilities = UsedInCapabilities.Add(p);
if (UsedInCapabilities.Contains(p)) return;

var newArray = UsedInCapabilities.Add(p);
Thread.MemoryBarrier(); // make sure the array is complete before we let other threads use it lock-free
UsedInCapabilities = newArray;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ protected void AddNullResolvers()



string? toStringValue;
volatile string? toStringValue;
public override string ToString()
{
if (toStringValue is null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ namespace DotVVM.Framework.Binding.Expressions
[Options]
public class CommandBindingExpression : BindingExpression, ICommandBinding
{
public CommandBindingExpression(BindingCompilationService service, IEnumerable<object> properties) : base(service, properties)
public CommandBindingExpression(BindingCompilationService service, IEnumerable<object?> properties) : base(service, properties)
{
AddNullResolvers();
}
Expand Down Expand Up @@ -172,6 +172,6 @@ public CommandBindingExpression(BindingCompilationService service, BindingDelega
public class CommandBindingExpression<T> : CommandBindingExpression, ICommandBinding<T>
{
public new BindingDelegate<T> BindingDelegate => base.BindingDelegate.ToGeneric<T>();
public CommandBindingExpression(BindingCompilationService service, IEnumerable<object> properties) : base(service, properties) { }
public CommandBindingExpression(BindingCompilationService service, IEnumerable<object?> properties) : base(service, properties) { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace DotVVM.Framework.Binding.Expressions
[Options]
public class ResourceBindingExpression : BindingExpression, IStaticValueBinding
{
public ResourceBindingExpression(BindingCompilationService service, IEnumerable<object> properties) : base(service, properties) { }
public ResourceBindingExpression(BindingCompilationService service, IEnumerable<object?> properties) : base(service, properties) { }

public BindingDelegate BindingDelegate => this.bindingDelegate.GetValueOrThrow(this);

Expand All @@ -34,7 +34,7 @@ public class OptionsAttribute : BindingCompilationOptionsAttribute

public class ResourceBindingExpression<T> : ResourceBindingExpression, IStaticValueBinding<T>
{
public ResourceBindingExpression(BindingCompilationService service, IEnumerable<object> properties) : base(service, properties) { }
public ResourceBindingExpression(BindingCompilationService service, IEnumerable<object?> properties) : base(service, properties) { }

public new BindingDelegate<T> BindingDelegate => base.BindingDelegate.ToGeneric<T>();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using DotVVM.Framework.Utils;
using System.Runtime.CompilerServices;
using System.Collections.Immutable;
using System.Threading;

namespace DotVVM.Framework.Compilation.ControlTree
{
Expand Down Expand Up @@ -81,8 +82,11 @@ internal void AddUsedInCapability(DotvvmCapabilityProperty? p)
if (p is object)
lock(this)
{
if (!UsedInCapabilities.Contains(p))
UsedInCapabilities = UsedInCapabilities.Add(p);
if (UsedInCapabilities.Contains(p)) return;

var newArray = UsedInCapabilities.Add(p);
Thread.MemoryBarrier();
UsedInCapabilities = newArray;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using DotVVM.Framework.Utils;
using DotVVM.Framework.Binding.Properties;
using DotVVM.Framework.Testing;
using Microsoft.Extensions.Logging;

namespace DotVVM.Framework.Compilation
{
Expand All @@ -19,14 +20,16 @@ public class DotvvmViewCompilationService : IDotvvmViewCompilationService
private readonly IControlBuilderFactory controlBuilderFactory;
private readonly CompilationTracer tracer;
private readonly IMarkupFileLoader markupFileLoader;
private readonly ILogger<DotvvmViewCompilationService>? log;
private readonly DotvvmConfiguration dotvvmConfiguration;

public DotvvmViewCompilationService(DotvvmConfiguration dotvvmConfiguration, IControlBuilderFactory controlBuilderFactory, CompilationTracer tracer, IMarkupFileLoader markupFileLoader)
public DotvvmViewCompilationService(DotvvmConfiguration dotvvmConfiguration, IControlBuilderFactory controlBuilderFactory, CompilationTracer tracer, IMarkupFileLoader markupFileLoader, ILogger<DotvvmViewCompilationService>? log = null)
{
this.dotvvmConfiguration = dotvvmConfiguration;
this.controlBuilderFactory = controlBuilderFactory;
this.tracer = tracer;
this.markupFileLoader = markupFileLoader;
this.log = log;
masterPages = new Lazy<ConcurrentDictionary<string, DotHtmlFileInfo>>(InitMasterPagesCollection);
controls = new Lazy<ImmutableArray<DotHtmlFileInfo>>(InitControls);
routes = new Lazy<ImmutableArray<DotHtmlFileInfo>>(InitRoutes);
Expand Down Expand Up @@ -108,7 +111,13 @@ public async Task<bool> CompileAll(bool buildInParallel = true, bool forceRecomp
}
}
var discoveredMasterPages = new ConcurrentDictionary<string, DotHtmlFileInfo>();

var maxParallelism = buildInParallel ? Environment.ProcessorCount : 1;
if (!dotvvmConfiguration.Debug && dotvvmConfiguration.Markup.ViewCompilation.Mode != ViewCompilationMode.DuringApplicationStart)
{
// in production when compiling after application start, only use half of the CPUs to leave room for handling requests
maxParallelism = (int)Math.Ceiling(maxParallelism * 0.5);
}
var sw = ValueStopwatch.StartNew();

var compilationTaskFactory = (DotHtmlFileInfo t) => () => {
BuildView(t, forceRecompile, out var masterPage);
Expand All @@ -117,15 +126,19 @@ public async Task<bool> CompileAll(bool buildInParallel = true, bool forceRecomp
};

var compileTasks = filesToCompile.Select(compilationTaskFactory).ToArray();
await ExecuteCompileTasks(compileTasks, buildInParallel);
var totalCompiledFiles = compileTasks.Length;
await ExecuteCompileTasks(compileTasks, maxParallelism);

while (discoveredMasterPages.Any())
{
compileTasks = discoveredMasterPages.Values.Select(compilationTaskFactory).ToArray();
totalCompiledFiles += compileTasks.Length;
discoveredMasterPages = new ConcurrentDictionary<string, DotHtmlFileInfo>();

await ExecuteCompileTasks(compileTasks, buildInParallel);
await ExecuteCompileTasks(compileTasks, maxParallelism);
}

log?.LogInformation("Compiled {0} DotHTML files on {1} threads in {2} s", totalCompiledFiles, maxParallelism, sw.ElapsedSeconds);
}
finally
{
Expand All @@ -135,11 +148,22 @@ public async Task<bool> CompileAll(bool buildInParallel = true, bool forceRecomp
return !GetFilesWithFailedCompilation().Any();
}

private async Task ExecuteCompileTasks(Action[] compileTasks, bool buildInParallel)
private static async Task ExecuteCompileTasks(Action[] compileTasks, int maxParallelism)
{
if (buildInParallel)
if (maxParallelism > 1)
{
await Task.WhenAll(compileTasks.Select(Task.Run));
var semaphore = new SemaphoreSlim(maxParallelism);
await Task.WhenAll(compileTasks.Select(async t => {
await semaphore.WaitAsync();
try
{
await Task.Run(t);
}
finally
{
semaphore.Release();
}
}));
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ public IEnumerable<MethodInfo> GetExtensionsForNamespaces(string[] @namespaces)
// it's most likely the same namespaces, so it won't help at all - only run into lock contention in System.Reflection
lock (methodsCache)
{
results = namespaces.Select(x => methodsCache.GetValueOrDefault(x)).ToArray();
var missingNamespaces = namespaces.Where(x => !methodsCache.ContainsKey(x)).ToArray();

var createdNamespaces = CreateExtensionsForNamespaces(missingNamespaces);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public static JsExpression Invoke(this JsExpression target, IEnumerable<JsExpres
public static JsExpression Invoke(this JsExpression target, params JsExpression?[] arguments) =>
new JsInvocationExpression(target, arguments);

public static JsExpression CallMethod(this JsExpression target, string methodName, params JsExpression?[] arguments) =>
target.Member(methodName).Invoke(arguments);

public static JsExpression Indexer(this JsExpression target, JsExpression argument)
{
return new JsIndexerExpression(target, argument);
Expand Down
Loading

0 comments on commit 59dea87

Please sign in to comment.