-
-
Notifications
You must be signed in to change notification settings - Fork 567
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
Add support for isolated execution context #789
Conversation
e66eb6e
to
d650cc0
Compare
Should we be able to use this without the dispose pattern, such that we can also do "LeaveIsolatedContext", so the engine would store the context and dispose it when it's called. Might be easier to make the pool implementation. |
I meant to keep the Dispose pattern, just that also being able to call Leave, that will invoke Dispose on the local context. |
How is |
|
||
// Can modify global scope | ||
engine.Execute("outer = 321"); | ||
engine.GetValue("outer").ToObject().Should().Be(321); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the value when the scope is released?
Why doesn't it throw an exception like Math.max
. Not saying it should, but how do we know it won't?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I explained this below wrt Math.max
, TLDR; we can rewrite and modify anything that can be attached to our isolated context - this is shadowing vars. outer = 32;
is effectively var outer = 321;
so introducing a new variable isolated environment hiding old from global.
Jint/Pooling/EnginePool.cs
Outdated
{ | ||
private readonly ObjectPool<Engine> _enginePool; | ||
|
||
public EnginePool(int size, Options options, Action<IPooledEngine> initialize) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Size should be the last argument.
It should not be mandatory.
Jint/Pooling/EnginePool.cs
Outdated
Engine engine; | ||
lock (_enginePool) | ||
{ | ||
engine = _enginePool.Allocate(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this method already thread-safe ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well they used to be 😉 When I brought the pool from MS pool package, we traded thread-safety for speed as Jint is not thread-safe. Would require second impl with the origimal Interlocked
functionality.
Jint/Pooling/EnginePool.cs
Outdated
} | ||
} | ||
|
||
private sealed class EngineWrapper : IPooledEngine |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe create an IEngine
instead that is implemented by Engine
too. And the pooled version could handle the dispose pattern, and have a Release/Return() method if we don't want to use the dispose pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can do, I just want to IEngine to strictly limited to execution and setting values to context. Might be role interface too. Just need to limit public access to things like ObjectConstructor
etc which are now public and might allow doing bad things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'll stick for wrapper now so that we can implement the dispose and checks here. Or should the Engine implement it as quite no-op for now and we would just start checking if (_disposed) { throw diposedexception}
in engine service methods?
Jint/Pooling/EnginePool.cs
Outdated
engine = _enginePool.Allocate(); | ||
} | ||
|
||
using (engine.EnterIsolatedContext()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the interface, the pool could return an IEngine directly, which would be the result of this call.
This is a bit tricky and probably counter-intuitive at first. When you update anything var-like which is bound to global scope ( But when you do So key difference is that doing |
9473562
to
62e5af4
Compare
Now we have support for The pool API also needs some love. |
62e5af4
to
cb95ca6
Compare
I don't see the point of IEngine if we have IScriptExecutor. These two methods in IEngine are used by the EngineWrapper, and it knows about Engine, so the interface seems useless. At least based on the fact that I don't see a user accessing them. Or I would just shift the interface names to
Can you share the results of the benchmarks? |
I think this PR is coming a bit of overwhelmed with all the needs. My original thinking was to clean up Maybe we need to think about having the There are a lot of things here, like why we expose |
I like it better. So anything that can be isolated should be in the interface that is returned by isolated pools, e.g. |
ade1384
to
d9f8260
Compare
Changed some names again, here's the results you asked: BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1)
AMD Ryzen 7 2700X, 1 CPU, 16 logical and 8 physical cores
.NET Core SDK=5.0.100-rc.2.20479.15
[Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
DefaultJob : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
|
d9f8260
to
2f6973b
Compare
I don't know how much of this is done and already working, but I wanted to mention a new ES proposal that has quite similar features. It is called Realms API. It is a stage 2 proposal and is subject to change. But I believe it will make it way into the standard as it is a commonly needed pattern in Javascript engines. So if you want to have an in-engine solution, you may consider Realms API. |
I was actually reading the specs just today! I'm investigating that API as a way to upgrade AngleSharp.JS to Jint 3 as they want window as global. So far it seems that Realms is only little bit lighter than new engine instance, here this PR tries to allow existing constructs to be reused. Probably needs more investigation. Thanks for the interest, I'm open to ideas and usage scenarios you have in mind! |
* introduce IScriptExecutor to narrow down interface * add engine factories and refine interfaces * make constraint disabling more generic, allow options to build constraints * support disabling constrains when initializing engine pool, fix statement counting * add test cases to show behavior * shape engine pool api
This looks really awesome!! What are the chances of this PR landing any time soon? |
I think we should get initial version of realms/host support first in. This will allow creating a custom host for isolated context (like a browser has) that can inject custom global and participate in execution context creation. |
Hmm... that sounds like it's going to be a long ways off. :-( |
@ejsmith what scenario is really important for you that is covered in this PR? |
We have a multi-tenanted app that uses JS for customization and we pool engine instances but I'm worried that the global scope could be altered and leak to other tenants. |
Sharing a pool between tenants can be troublesome indeed, just needs a code like this to run For what's it worth, we did improve the engine creation performance a while back, but if you have a lot of initialization going on might not suffice for your use case. |
Created #907 for the preliminary work. |
Hello, Out of curiosity, are there any plans for when this might become available? |
I think we've created a Duke Nukem project here, let's see when it's ready. |
ShadowRealm was merged to main and it might contain the functionality aimed in this PR, see Jint test case how it isolates a playground from the actual engine. |
I think |
thanks for the update, @lahma. |
@gentledepp it was moved to public API test project: https://github.com/sebastienros/jint/blob/main/Jint.Tests.PublicInterface/ShadowRealmTests.cs |
Thank you so much! This is awesome! |
This seems to work now quite nicely, at least as a proof of concept. A disposable context can be created which allows introducing variables and whatnot and when context is disposed they disappear. Also sets everything resolved from outer context (say the real global) as read-only and throws errors if you try to do something like
Math.max = null;
.Here's the code sample from test case demonstrating the functionality:
When coupled with default engine pool implementation (to be done) this should allow the common pattern people request, a pre-warmed engine with scripts that can then be called with temporary catching context that is cleaned after the call.
Relates to
fixes #738
fixes #805