Skip to content

Commit

Permalink
Compile all expressions at once
Browse files Browse the repository at this point in the history
  • Loading branch information
aoltean16 committed Jun 19, 2023
1 parent 4f54f5f commit 0b21f8f
Show file tree
Hide file tree
Showing 24 changed files with 678 additions and 418 deletions.
65 changes: 64 additions & 1 deletion src/Test/TestCases.Workflows/ExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Activities.Validation;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;

namespace TestCases.Workflows;
Expand Down Expand Up @@ -47,7 +48,8 @@ static ExpressionTests()
// There's no programmatic way (that I know of) to add assembly references when creating workflows like in these tests.
// Adding the custom assembly directly to the expression validator to simulate XAML reference.
// The null is for testing purposes.
VbExpressionValidator.Instance = new VbExpressionValidator(new() { typeof(ClassWithCollectionProperties).Assembly, null });
VbExpressionValidator.Instance.AddRequiredAssembly(typeof(ClassWithCollectionProperties).Assembly);
CSharpExpressionValidator.Instance.AddRequiredAssembly(typeof(ClassWithCollectionProperties).Assembly);
}

[Theory]
Expand Down Expand Up @@ -424,6 +426,67 @@ public void CSRoslynValidator_ValidatesMoreThan16Arguments()
var result = ActivityValidationServices.Validate(sequence, _useValidator);
result.Errors.ShouldBeEmpty();
}
[Fact]
public void VB_Multithreaded_NoError()
{
var activities = new List<Activity>();
var tasks = new List<Task>();
var results = new List<ValidationResults>();
for (var i = 0; i < 20; i++)
{
var seq = new Sequence();
seq.Variables.Add(new Variable<int>("sum"));
for (var j = 0; j < 10000; j++)
{
seq.Activities.Add(new Assign
{
To = new OutArgument<int>(new VisualBasicReference<int>("sum")),
Value = new InArgument<int>(new VisualBasicValue<int>($"sum + {j}"))
});
}
}
foreach (var activity in activities)
{
tasks.Add(Task.Run(() =>
{
results.Add(ActivityValidationServices.Validate(activity, _useValidator));
}));
}
Task.WaitAll(tasks.ToArray());

results.All(r => !r.Errors.Any() && !r.Warnings.Any()).ShouldBeTrue();
}

[Fact]
public void CS_Multithreaded_NoError()
{
var activities = new List<Activity>();
var tasks = new List<Task>();
var results = new List<ValidationResults>();
for (var i = 0; i < 20; i++)
{
var seq = new Sequence();
seq.Variables.Add(new Variable<int>("sum"));
for (var j = 0; j < 10000; j++)
{
seq.Activities.Add(new Assign
{
To = new OutArgument<int>(new CSharpReference<int>("sum")),
Value = new InArgument<int>(new CSharpValue<int>($"sum + {j}"))
});
}
}
foreach (var activity in activities)
{
tasks.Add(Task.Run(() =>
{
results.Add(ActivityValidationServices.Validate(activity, _useValidator));
}));
}
Task.WaitAll(tasks.ToArray());

results.All(r => !r.Errors.Any() && !r.Warnings.Any()).ShouldBeTrue();
}

[Fact]
public void VBReferenceTypeIsChecked()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ internal sealed class ActivityLocationReferenceEnvironment : LocationReferenceEn
private Dictionary<string, LocationReference> _declarations;
private List<LocationReference> _unnamedDeclarations;

public ActivityLocationReferenceEnvironment() { }
public ActivityLocationReferenceEnvironment()
{
Extensions = new();
}

public ActivityLocationReferenceEnvironment(LocationReferenceEnvironment parent)
{
Expand All @@ -22,6 +25,7 @@ public ActivityLocationReferenceEnvironment(LocationReferenceEnvironment parent)
CompileExpressions = parent.CompileExpressions;
IsValidating = parent.IsValidating;
InternalRoot = parent.Root;
Extensions = parent.Extensions;
}
}

Expand Down
74 changes: 74 additions & 0 deletions src/UiPath.Workflow.Runtime/EnvironmentExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// This file is part of Core WF which is licensed under the MIT license.
// See LICENSE file in the project root for full license information.

namespace System.Activities
{
internal class EnvironmentExtensions
{
private readonly Dictionary<Type, object> _extensions = new();

/// <summary>
/// Gets the specified extension.
/// If the extension does not exist,
/// it will invoke the <paramref name="createExtensionFactory"/> parameter
/// </summary>
/// <typeparam name="T">The type of the extension</typeparam>
/// <param name="createExtensionFactory">The factory to create the extension</param>
/// <exception cref="ArgumentNullException"></exception>
public TInterface GetOrAdd<TInterface>(Func<TInterface> createExtensionFactory)
where TInterface : class
{
var type = typeof(TInterface);
if (_extensions.TryGetValue(type, out object extension))
{
return extension as TInterface;
}

return CreateAndAdd(createExtensionFactory, type);
}

/// <summary>
/// Retrieves the extension registered for the given type
/// or null otherwise
/// </summary>
/// <typeparam name="T">The type of the extension.</typeparam>
public T Get<T>() where T : class
{
if (_extensions.TryGetValue(typeof(T), out object extension))
return extension as T;
return null;
}

/// <summary>
/// Adds the specified extension to the list.
/// The extension is treated as a singleton,
/// so if a second extension with the same type is added, it will
/// throw an <see cref="InvalidOperationException"/>
/// </summary>
/// <typeparam name="TInterface">The type of the extension</typeparam>
/// <param name="extension">The extension</param>
/// <exception cref="InvalidOperationException"></exception>
public void Add<TInterface, TImplementation>(TImplementation extension)
where TInterface : class
where TImplementation : class, TInterface
{
if (_extensions.ContainsKey(typeof(TInterface)))
throw new InvalidOperationException($"Service '{typeof(TInterface).FullName}' already exists");

_extensions[typeof(TInterface)] = extension;
}


#region private methods
private T CreateAndAdd<T>(Func<T> createExtensionFactory, Type type) where T : class
{
var extension = createExtensionFactory();
if (extension is null)
throw new ArgumentNullException(nameof(extension));

_extensions[type] = extension;
return extension;
}
#endregion
}
}
2 changes: 2 additions & 0 deletions src/UiPath.Workflow.Runtime/LocationReferenceEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public abstract class LocationReferenceEnvironment
/// </summary>
internal bool IsValidating { get; set; }

internal EnvironmentExtensions Extensions { get; set; }

public abstract Activity Root { get; }

public LocationReferenceEnvironment Parent { get; protected set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ namespace System.Activities.Validation;

public static class ActivityValidationServices
{
internal static readonly ReadOnlyCollection<Activity> EmptyChildren = new(Array.Empty<Activity>());
private static readonly ValidationSettings defaultSettings = new();

internal static readonly ReadOnlyCollection<Activity> EmptyChildren = new(Array.Empty<Activity>());
internal static ReadOnlyCollection<ValidationError> EmptyValidationErrors = new(new List<ValidationError>(0));

public static ValidationResults Validate(Activity toValidate) => Validate(toValidate, defaultSettings);
Expand Down Expand Up @@ -245,7 +246,7 @@ private static string GenerateExceptionString(IList<ValidationError> validationE
return exceptionString;
}

static internal string GenerateValidationErrorPrefix(Activity toValidate, ActivityUtilities.ActivityCallStack parentChain, ProcessActivityTreeOptions options, out Activity source)
internal static string GenerateValidationErrorPrefix(Activity toValidate, ActivityUtilities.ActivityCallStack parentChain, ProcessActivityTreeOptions options, out Activity source)
{
bool parentVisible = true;
string prefix = "";
Expand Down Expand Up @@ -453,9 +454,28 @@ internal ValidationResults InternalValidate()
ActivityUtilities.CacheRootMetadata(_rootToValidate, _environment, _options, new ActivityUtilities.ProcessActivityCallback(ValidateElement), ref _errors);
}

var extension = GetValidationExtension(_environment);
if (extension is not null)
{
var results = extension.Validate(_rootToValidate);
if (_errors?.Any() == true)
{
foreach (var error in _errors)
{
results.Add(error);
}
}
return new ValidationResults(results);
}

return new ValidationResults(_errors);
}

private static IValidationExtension GetValidationExtension(LocationReferenceEnvironment environment)
{
return environment.Extensions.Get<IValidationExtension>();
}

private void ValidateElement(ActivityUtilities.ChildActivity childActivity, ActivityUtilities.ActivityCallStack parentChain)
{
Activity toValidate = childActivity.Activity;
Expand Down
15 changes: 15 additions & 0 deletions src/UiPath.Workflow.Runtime/Validation/ExpressionToValidate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

namespace System.Activities.Validation;

internal sealed class ExpressionToValidate
{
public Activity Activity { get; init; }

public string ExpressionText { get; init; }

public LocationReferenceEnvironment Environment { get; init; }

public Type ResultType { get; init; }

public bool IsLocation { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace System.Activities.Validation
{
internal interface IValidationExtension
{
IList<ValidationError> Validate(Activity activity);

void QueueExpressionForValidation<T>(ExpressionToValidate expressionToValidate, string language);
}
}
29 changes: 29 additions & 0 deletions src/UiPath.Workflow.Runtime/Validation/ValidationScope.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Collections.Immutable;

namespace System.Activities.Validation
{
internal sealed class ValidationScope
{
private readonly Dictionary<string, ExpressionToValidate> _expressionsToValidate = new();
private string _language;

internal void AddExpression<T>(ExpressionToValidate expressionToValidate, string language)
{
_language ??= language;
if (_language != language)
{
expressionToValidate.Activity.AddTempValidationError(new ValidationError(SR.DynamicActivityMultipleExpressionLanguages(language), expressionToValidate.Activity));
return;
}
_expressionsToValidate.Add(expressionToValidate.Activity.Id, expressionToValidate);
}

internal string Language => _language;

internal ExpressionToValidate GetExpression(string activityId) => _expressionsToValidate[activityId];

internal ImmutableArray<ExpressionToValidate> GetAllExpressions() => _expressionsToValidate.Values.ToImmutableArray();

internal void Clear() => _expressionsToValidate.Clear();
}
}
Loading

0 comments on commit 0b21f8f

Please sign in to comment.