Skip to content

Allow concurrent calls to Engine #673

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Jint.Tests/Jint.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.0;net452</TargetFrameworks>
<TargetFrameworks>netcoreapp3.0;net46</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Runtime\Scripts\*.*;Parser\Scripts\*.*" />
Expand All @@ -9,7 +9,7 @@
<ProjectReference Include="..\Jint\Jint.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" Condition=" '$(TargetFramework)' == 'net452' " />
<Reference Include="Microsoft.CSharp" Condition=" '$(TargetFramework)' == 'net46' " />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
Expand Down
45 changes: 45 additions & 0 deletions Jint.Tests/Runtime/EngineTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Esprima;
using Esprima.Ast;
using Jint.Native;
Expand Down Expand Up @@ -726,6 +728,49 @@ public void ShouldThrowStatementCountOverflow()
);
}

[Fact]
public async Task ShouldHandleConcurrentCallsUsingInvoke()
{

var script = @"var recurse = function(n) {
if (n <= 0) {
return;
}
recurse(n - 1)
};";

var engine = new Engine(cfg => cfg.LimitRecursion(10));
engine.Execute(script);

// Should not throw
await Task.WhenAll(Enumerable.Range(0, 20).Select(async _ =>
{
await Task.Yield();
engine.Invoke("recurse", 10);
}));
}

[Fact]
public async Task ShouldHandleConcurrentCallsUsingExecute()
{
var script = @"var recurse = function(n) {
if (n <= 0) {
return;
}
recurse(n - 1)
};";

var engine = new Engine(cfg => cfg.LimitRecursion(10));
engine.Execute(script);

// Should not throw
await Task.WhenAll(Enumerable.Range(0, 20).Select(async _ =>
{
await Task.Yield();
engine.Execute("recurse(10)");
}));
}

[Fact]
public void ShouldThrowMemoryLimitExceeded()
{
Expand Down
127 changes: 71 additions & 56 deletions Jint/Engine.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Esprima;
using Esprima.Ast;
using Jint.Native;
Expand Down Expand Up @@ -30,6 +33,7 @@
using Jint.Runtime.Interop;
using Jint.Runtime.Interpreter;
using Jint.Runtime.References;
using ExecutionContext = Jint.Runtime.Environments.ExecutionContext;

namespace Jint
{
Expand All @@ -51,7 +55,6 @@ public class Engine
private static readonly JsString _typeErrorFunctionName = new JsString("TypeError");
private static readonly JsString _uriErrorFunctionName = new JsString("URIError");

private readonly ExecutionContextStack _executionContexts;
private JsValue _completionValue = JsValue.Undefined;
private int _statementsCount;
private long _initialMemoryUsage;
Expand Down Expand Up @@ -83,7 +86,7 @@ public class Engine
public ITypeConverter ClrTypeConverter { get; set; }

// cache of types used when resolving CLR type names
internal readonly Dictionary<string, Type> TypeCache = new Dictionary<string, Type>();
internal readonly IDictionary<string, Type> TypeCache = new ConcurrentDictionary<string, Type>();

internal static Dictionary<Type, Func<Engine, object, JsValue>> TypeMappers = new Dictionary<Type, Func<Engine, object, JsValue>>
{
Expand Down Expand Up @@ -143,29 +146,73 @@ public override int GetHashCode()
}
}

internal readonly Dictionary<ClrPropertyDescriptorFactoriesKey, Func<Engine, object, PropertyDescriptor>> ClrPropertyDescriptorFactories =
new Dictionary<ClrPropertyDescriptorFactoriesKey, Func<Engine, object, PropertyDescriptor>>();
internal readonly IDictionary<ClrPropertyDescriptorFactoriesKey, Func<Engine, object, PropertyDescriptor>> ClrPropertyDescriptorFactories =
new ConcurrentDictionary<ClrPropertyDescriptorFactoriesKey, Func<Engine, object, PropertyDescriptor>>();


private AsyncLocal<JintCallStack> _callStackLocal = new AsyncLocal<JintCallStack>();
internal JintCallStack CallStack { get => _callStackLocal.Value; private set => _callStackLocal.Value = value; }


private AsyncLocal<ExecutionContext> _executionContextLocal = new AsyncLocal<ExecutionContext>();
public ExecutionContext ExecutionContext { get => _executionContextLocal.Value; private set => _executionContextLocal.Value = value; }

internal readonly JintCallStack CallStack = new JintCallStack();

static Engine()
{
var methodInfo = typeof(GC).GetMethod("GetAllocatedBytesForCurrentThread");

if (methodInfo != null)
{
GetAllocatedBytesForCurrentThread = (Func<long>)Delegate.CreateDelegate(typeof(Func<long>), null, methodInfo);
GetAllocatedBytesForCurrentThread = (Func<long>)Delegate.CreateDelegate(typeof(Func<long>), null, methodInfo);
}
}

internal Task<T> WithExecutionContext<T>(
LexicalEnvironment lexicalEnvironment,
LexicalEnvironment variableEnvironment,
JsValue thisBinding,
Func<T> call)
{
return WithExecutionContext(
lexicalEnvironment,
variableEnvironment,
thisBinding,
() => Task.FromResult(call()));
}

internal async Task<T> WithExecutionContext<T>(
LexicalEnvironment lexicalEnvironment,
LexicalEnvironment variableEnvironment,
JsValue thisBinding,
Func<Task<T>> call)
{
var context = new ExecutionContext(
lexicalEnvironment,
variableEnvironment,
thisBinding);

ExecutionContext = context;
return await call().ConfigureAwait(false);
}

internal Task<T> ExecuteCallAsync<T>(CallStackElement element, Func<T> call)
{
return ExecuteCallAsync(element, () => Task.FromResult(call()));
}

internal async Task<T> ExecuteCallAsync<T>(CallStackElement element, Func<Task<T>> call)
{
CallStack = new JintCallStack(CallStack, element);
return await call().ConfigureAwait(false);
}

public Engine() : this(null)
{
}

public Engine(Action<Options> options)
{
_executionContexts = new ExecutionContextStack(2);

Global = GlobalObject.CreateGlobalObject(this);

Object = ObjectConstructor.CreateObjectConstructor(this);
Expand Down Expand Up @@ -198,7 +245,12 @@ public Engine(Action<Options> options)
GlobalEnvironment = LexicalEnvironment.NewObjectEnvironment(this, Global, null, false);

// create the global execution context http://www.ecma-international.org/ecma-262/5.1/#sec-10.4.1.1
EnterExecutionContext(GlobalEnvironment, GlobalEnvironment, Global);
ExecutionContext = new ExecutionContext(
GlobalEnvironment,
GlobalEnvironment,
Global);

CallStack = new JintCallStack(null, null);

Options = new Options();

Expand All @@ -210,7 +262,7 @@ public Engine(Action<Options> options)
_maxStatements = Options._MaxStatements;
_referenceResolver = Options.ReferenceResolver;
_memoryLimit = Options._MemoryLimit;
_runBeforeStatementChecks = (_maxStatements > 0 &&_maxStatements < int.MaxValue)
_runBeforeStatementChecks = (_maxStatements > 0 && _maxStatements < int.MaxValue)
|| Options._TimeoutInterval.Ticks > 0
|| _memoryLimit > 0
|| _isDebugMode;
Expand Down Expand Up @@ -260,12 +312,6 @@ public Engine(Action<Options> options)
public ErrorConstructor ReferenceError => _referenceError ?? (_referenceError = ErrorConstructor.CreateErrorConstructor(this, _referenceErrorFunctionName));
public ErrorConstructor UriError => _uriError ?? (_uriError = ErrorConstructor.CreateErrorConstructor(this, _uriErrorFunctionName));

public ref readonly ExecutionContext ExecutionContext
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _executionContexts.Peek();
}

public GlobalSymbolRegistry GlobalSymbolRegistry { get; }

internal Options Options { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
Expand Down Expand Up @@ -293,19 +339,6 @@ public ref readonly ExecutionContext ExecutionContext

private static readonly Func<long> GetAllocatedBytesForCurrentThread;

public void EnterExecutionContext(
LexicalEnvironment lexicalEnvironment,
LexicalEnvironment variableEnvironment,
JsValue thisBinding)
{
var context = new ExecutionContext(
lexicalEnvironment,
variableEnvironment,
thisBinding);

_executionContexts.Push(context);
}

public Engine SetValue(in Key name, Delegate value)
{
Global.FastAddProperty(name, new DelegateWrapper(this, value), true, false, true);
Expand Down Expand Up @@ -342,12 +375,6 @@ public Engine SetValue(in Key name, object obj)
{
return SetValue(name, JsValue.FromObject(this, obj));
}

public void LeaveExecutionContext()
{
_executionContexts.Pop();
}

/// <summary>
/// Initializes the statements count
/// </summary>
Expand All @@ -370,14 +397,6 @@ public void ResetTimeoutTicks()
_timeoutTicks = timeoutIntervalTicks > 0 ? DateTime.UtcNow.Ticks + timeoutIntervalTicks : 0;
}

/// <summary>
/// Initializes list of references of called functions
/// </summary>
public void ResetCallStack()
{
CallStack.Clear();
}

public Engine Execute(string source)
{
return Execute(source, DefaultParserOptions);
Expand All @@ -400,9 +419,8 @@ public Engine Execute(Program program)

ResetTimeoutTicks();
ResetLastStatement();
ResetCallStack();

using (new StrictModeScope(_isStrict || program.Strict))
StrictModeScope.WithStrictModeScope(() =>
{
DeclarationBindingInstantiation(
DeclarationBindingType.GlobalCode,
Expand All @@ -419,7 +437,10 @@ public Engine Execute(Program program)
}

_completionValue = result.GetValueOrDefault();
}

return true;

}, (_isStrict || program.Strict)).GetAwaiter().GetResult();

return this;
}
Expand Down Expand Up @@ -488,7 +509,7 @@ internal JsValue GetValue(object value, bool returnReferenceToPool)

if (!(value is Reference reference))
{
return ((Completion) value).Value;
return ((Completion)value).Value;
}

return GetValue(reference, returnReferenceToPool);
Expand Down Expand Up @@ -549,7 +570,7 @@ internal JsValue GetValue(Reference reference, bool returnReferenceToPool)
return Undefined.Instance;
}

var callable = (ICallable) getter.AsObject();
var callable = (ICallable)getter.AsObject();
return callable.Call(baseValue, Arguments.Empty);
}
}
Expand Down Expand Up @@ -589,7 +610,7 @@ public void PutValue(Reference reference, JsValue value)
var baseValue = reference._baseValue;
if (reference._baseValue._type == Types.Object || reference._baseValue._type == Types.None)
{
((ObjectInstance) baseValue).Put(referencedName, value, reference._strict);
((ObjectInstance)baseValue).Put(referencedName, value, reference._strict);
}
else
{
Expand All @@ -599,7 +620,7 @@ public void PutValue(Reference reference, JsValue value)
else
{
var baseValue = reference._baseValue;
((EnvironmentRecord) baseValue).SetMutableBinding(referencedName, value, reference._strict);
((EnvironmentRecord)baseValue).SetMutableBinding(referencedName, value, reference._strict);
}
}

Expand Down Expand Up @@ -877,12 +898,6 @@ private void AddFunctionDeclarations(
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void UpdateLexicalEnvironment(LexicalEnvironment newEnv)
{
_executionContexts.ReplaceTopLexicalEnvironment(newEnv);
}

private static void AssertNotNullOrEmpty(string propertyName, string propertyValue)
{
if (string.IsNullOrEmpty(propertyValue))
Expand Down
Loading