-
Notifications
You must be signed in to change notification settings - Fork 127
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
Find a way to apply a execution timeout even for code in protected regions #85
Comments
A less invasive (and probably more clean) alternative would be to implement a cancellation check directly in the generated code. This would avoid the need to use For example:
private void HandleCancellationRequest() {
CancellationExceptionThrown = true;
throw new ScriptCancellationException();
}
if (Volatile.Read(ref engine.cancellationRequested))
HandleCancellationRequest();
if (!engine.cancellationExceptionThrown) {
// Code
} Then, another thread can set the This would of course be slower due to the need for checking the flags, so these checks probably shouldn't be compiled into the code unless the user explicitely enables a compiler option. I think it is sufficient to check only at the beginning of functions and loops, but not after every single statetment (like What do you think? |
…tionRequested property. (paulbartrum#85)
Hi @paulbartrum, I have committed the above feature to my branch implementScriptTimeout3 (eventually I'd like to submit a PR if you agree with the changes). The user can now set the property When using a simple loop like var dbl = 2;
for (var i = 0; i < 30000000; i++) {
dbl *= 1.0000001;
} execution time increases by about 14% on my machine when enabling To ensure However, I ran into a problem with the catch (JavaScriptException ex) when (ex.ScriptEngine == engine) For this, I'm using a filter block before the catch block, which requires that generator.BeginFilterBlock();
// Check the exception and load a Int32 on the stack...
generator.BeginCatchBlock(null);
// Handle the exception... This is working fine with the public override void BeginCatchBlock(Type exceptionType)
{
if (exceptionType == null)
throw new ArgumentNullException("exceptionType"); I cannot simply remove the check because later Do you have any idea how to solve this, so that Thank you! |
Hmm, I found out that on a |
… the generated code, but in the caller. This is because ILGenerator.BeginExceptFilterBlock() is not supported if the generator belongs to a DynamicMethod. Completes paulbartrum#85
I've checked in a fix for this issue. I copied most of your code, so thanks for that. I didn't copy |
Hi @paulbartrum, I see that you have applied the fix to not run JS I think using an exception filter might a bit faster if there are multiple However, I think your change is not yet complete: The Note: This does not yet solve the original problem of this issue, which is that aborting a thread is delayed until a protected region ( Beacuse the flag needs to be checked frequently, it has an impact on performance (which is why the cancellation checks aren't enabled by default), but unfortunately I can't see another way of solving this problem, except than splitting What do you think? Should I submit a PR to implement the cancellation checks? In this case I could update the wiki page about the script timeout (and then setting a timeout should also work on .NET Core where Thanks! |
Ah, you're right, I missed the If I seem a bit hesitant about some of these changes, it's because I'm doubtful it can be made robust. The only truly robust way of safely running user code is in a separate process IMO (this is what the Test Suite Runner project does). By robust I mean that the user code will be stopped immediately 100% of time, with zero impact on the hosting code, even if the user code is "hostile". I am of course aware that running JS in a separate process has it's own problems. UWP doesn't support background processes, for one. It also means that all comms have to go over pipes or some other RPC protocol, which is slow and somewhat finicky. Still, I can't help but think we're encouraging bad practices by publishing the Thoughts? |
Hi @paulbartrum, I still think there should be a way to cancel script execution. In our case (which I described in #83), it would be extremely difficult and costly to run scripts in another process. For example, in the application there can be multiple scripts which are active (which means some callback of these scripts may be called soon). If we would run the scripts in a separate process, we would need an individual process for each script to maintain each script's state even if one script is cancelled by terminating the process. So if the user created 50 scripts, we would need to start 50 processes, which is very expensive in resource usage. Therefore, at least in our case I can't see a way to avoid cancelling script execution. Because Currently, I added the check only at the beginning of loop statements and at the top of generated methods. This might be sufficient if the script code length is limited, but maybe the check should be executed after every statement or expression. What do you think? |
Thanks for the detailed reply. You've convinced me that there are definitely use cases that cannot be solved using multi-process, but I need to mull on the best way to implement this. |
Hi @paulbartrum, |
…tionRequested property. (paulbartrum#85)
Hi,
some time ago I opened #42 to write a wiki page that shows how to apply a timeout to script execution.
Just now I was looking at the emit for the TryCatchFinallyStatement and realized that it emits JS
catch
andfinally
blocks as .NETcatch
andfinally
blocks. This means the timeout (which is done by Thread.Abort()) can easily be bypassed:Drat!
Also, when the script calls an API in the catch or finally block which uses
ScriptTimeoutHelper.EnterCriticalSection
, it will lead to a deadlock as that method waits for aThreadAbortException
, which will not happen since the code is currently in a protected region.Further, this means that JS code in a
finally
block is executed even when non-JavaScriptException
s, e.g. a ThreadAbortException or a StackOverflowException from the recursion depth check, are thrown (whereas in my case I would like to ensure no more script code is executed when throwing a non-JavaScriptException).Unfortunately, I'm not sure if it is somehow possible to mark generated catch and finally blocks as non-protected region, so that the CLR does not delay thread aborts for these regions. If this is not possible, the only way I can think of which would still be practically, is to generate functions for the contents of a
try
andcatch
statement, and catching an exception before running the code for acatch
andfinally
clause.For example, if the JS is this:
the generated code would look in C# like this:
(although that seems already quite a bit complicated).
Unfortunately, I also don't know much of the internals of CIL, so I'm not sure if I can do this.
Do you have any other idea how this could be solved?
Thanks!
The text was updated successfully, but these errors were encountered: