欢迎光临 Rick 's BLOG
日志首页  | C# .Net编程  | 原创作品  | 生活点滴  | C\C++相关  | 多媒体相关※ERMP  | VB相关  | 其它运维与编程  |  留言簿
介绍一个C#的javascpript引擎 Jurassic 解决C#编译问题 CS 1617:/langversion的选项“10”无效
未知  Jurassic [C# .Net JavaScript引擎]限制脚本执行时间,防止死循环
[ 发布日期:12个月前 (12-01) ]   [ 来自:本站原创 ] [分类:C# .Net编程]
想象一下在js引擎中执行如下代码:
[复制到剪贴板]

try {}
finally {
    while (
true);} 


[复制到剪贴板]

 while (true);  


这样一个无限循环的js代码在 ScriptEngine.Execute(String code)的调用中将永远不会返回。

Jurassic没有本身没有实现限制脚本执行时间的功能。由于Jurassic将JavaScript方法编译为IL代码,没有简单的方式来实现超时功能而不影响性能。但是,我们可以在执行脚本的线程中调用Thread.Abort()引发ThreadAbortException来终止脚本的执行。

一种实现方式:


在新线程中执行 ScriptEngine.Execute(),在当前线程中进行等待并计时。当新线程在规定的时间内未完成,则在当前线程中调用
thread.Abort()来终止新线程,并抛出一个超时的异常(TimeOutException)。


用于限制脚本执行时间的Helper类(ScriptTimeoutHelper)


[复制到剪贴板]

using System;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;

namespace 
JurassicTimeoutHelper
{
    
/// <summary>
    /// Allows to limit execution time of a Jurassic Script by internally using the
    /// technique of aborting a thread.
    /// </summary>
    
public class ScriptTimeoutHelper
    
{
        private 
HandlerState currentState;

        public 
ScriptTimeoutHelper()
        {
        }

        
/// <summary>
        /// Runs the specified <see cref="Action"/> in the current thread,
        /// applying the given timeout.
        /// When the handler times out, a <see cref="ThreadAbortException"/> is
        /// raised in the current thread to break. However, this is managed
        /// internally so it does not affect the caller of this method (i.e. it is
        /// ensured that a <see cref="ThreadAbortException"/> does not flow through
        /// this method or raised after this method returns).
        /// </summary>
        /// <exception cref="TimeoutException">Thrown when the handler times 
        /// out.</exception>
        /// <param name="handler"></param>
        /// <param name="timeout"></param>
        
public void RunWithTimeout(Action handlerint timeout)
        {
            if (
currentState != null)
                throw new 
InvalidOperationException(
                    $
"Cannot recursively call {nameof(RunWithTimeout)}.");
            if (
handler == null)
                throw new 
ArgumentException(nameof(handler));

            
// Throw the TimeoutException immediately when the timeout is 0.
            
if (timeout == 0)
                throw new 
TimeoutException();

            
ExceptionDispatchInfo caughtException null;

            
using (var state currentState =
                new 
HandlerState(Thread.CurrentThread))
            {
                
/* Start a monitoring task that may abort the current thread after
                 * the specified time limit.
                 * Note that the task will start immediately. Therefore we need to
                 * ensure the task does not abort the thread until we entered the
                 * try clause; otherwise the ThreadAbortException might fly through
                 * the caller of this method. To ensure this, the monitoring task
                 * waits until we release the semaphore the first time before
                 * actually waiting for the specified time.
                */
                
using (var monitoringTask Task.Run(async () =>
                    
await RunMonitoringTask(statetimeout)))
                {
                    try
                    {
                        
bool waitForAbortException;
                        try
                        {
                            
// Allow the monitoring task to begin by releasing the
                            // semaphore the first time.
                            // Do this in a finally block to ensure if this thread
                            // is aborted by other code, the semaphore is still
                            // released.
                            
try { }
                            finally
                            {
                                
state.WaitSemaphore.Release();
                            }

                            
// Execute the handler.
                            
handler();
                        }
                        catch (
Exception exwhen (!(ex is ThreadAbortException))
                        {
                            
/* Need to catch all exceptions (except our own
                             * ThreadAbortException) because we may wait for a
                             * ThreadAbortException to be thrown which is not
                             * possible in a finally handler.
                             */
                            
caughtException ExceptionDispatchInfo.Capture(ex);
                        }
                        finally
                        {
                            
/* Indicate that the handler is completed, and check
                             * if we need to wait for the ThreadAbortException.
                             * This is done in a finally handler to ensure when
                             * other code wants to abort this thread, the thread
                             * actually will abort as expected but we still can
                             * notify the monitoring task that we already returned.
                             */
                            
lock (state)
                            {
                                
state.IsExited true;
                                
waitForAbortException =
                                    
state.AbortState == AbortState.IsAborting;

                                if (
state.AbortState == AbortState.None)
                                {
                                    
// If the monitoring task did not do anything
                                    // yet, allow it to complete immediately.
                                    
state.WaitSemaphore.Release();
                                }
                            }
                        }

                        if (
waitForAbortException)
                        {
                            
/* The monitoring task indicated that it will abort our
                             * thread (but the ThreadAbortException did not yet
                             * occur), so we need to wait for the
                             * ThreadAbortException.
                             * This wait is needed because otherwise we may return
                             * too early (and in the finally block we wait for the
                             * monitoring task, causing a deadlock).
                             */
                            
Thread.Sleep(Timeout.Infinite);
                        }
                    }
                    catch (
ThreadAbortException ex)
                        
when (ex.ExceptionState == state)
                    {
                        
// Reset the abort.
                        
Thread.ResetAbort();

                        
// Indicate that the timeout has been exceeded.
                        
throw new TimeoutException();
                    }
                    finally
                    {
                        
// Wait for the monitoring task to complete.
                        
monitoringTask.Wait();
                        
currentState null;
                    }
                }
            }

            
// Check if we need to rethrow a caught exception (preserving the
            // original stacktrace).
            
if (caughtException != null)
                
caughtException.Throw();
        }

        private 
async Task RunMonitoringTask(HandlerState stateint timeout)
        {
            
// Wait until the handler thread entered the try-block.
            // Use a synchronous wait because we expect this to be a very short
            // period of time.
            
state.WaitSemaphore.Wait();

            
// Now asynchronously wait until the specified time has passed or the
            // semaphore has been released. In the latter case there is no need to
            // call AbortExecution().
            
bool completed await state.WaitSemaphore.WaitAsync(timeout);

            
// Abort the handler thread.
            
if (!completed)
                
AbortExecution(state);
        }

        private 
void AbortExecution(HandlerState state)
        {
            
bool canAbort;
            
lock (state)
            {
                if (
state.IsExited)
                {
                    
// The handler has already exited.
                    
return;
                }

                
// Check if we can call Thread.Abort() or if the handler thread is
                // currently in a critical section and needs to abort himself when
                // leaving the critical section.
                
canAbort = !state.IsCriticalSection;
                
state.AbortState canAbort AbortState.IsAborting
                    
AbortState.ShouldAbort;
            }
            if (
canAbort)
            {
                
/* The handler thread is not in a critical section so we can
                 * directly abort it.
                 * This needs to be done outside of the lock because Abort() could
                 * block if the  thread is currently in a finally handler (and
                 * trying to lock on the state object), which could lead to a
                 * deadlock.
                 */
                
state.HandlerThread.Abort(state);
            }
        }

        
/// <summary>
        /// Notifies this class that the handler thread is entering a critical
        /// section in which aborting the thread could corrupt the system's state.
        /// This means aborting the thread will be deferred until leaving the
        /// critical section.
        /// Note that you must call <see cref="ExitCriticalSection"/> in a
        /// <c>finally</c> block once the thread left the critical section.
        /// </summary>
        
public void EnterCriticalSection()
        {
            if (
currentState == null)
                throw new 
InvalidOperationException();

            
bool waitForAbortException;
            
lock (currentState)
            {
                if (
Thread.CurrentThread != currentState.HandlerThread
                    
|| currentState.IsCriticalSection)
                    throw new 
InvalidOperationException();

                
currentState.IsCriticalSection true;
                
waitForAbortException =
                    
currentState.AbortState == AbortState.IsAborting;
            }
            if (
waitForAbortException)
            {
                
// The monitoring task indicated that it will abort our thread, so
                // we need to wait for the ThreadAbortException.
                
Thread.Sleep(Timeout.Infinite);
            }
        }

        public 
void ExitCriticalSection()
        {
            if (
currentState == null)
                throw new 
InvalidOperationException();

            
bool shouldAbort;
            
lock (currentState)
            {
                if (
Thread.CurrentThread != currentState.HandlerThread
                    
|| !currentState.IsCriticalSection)
                    throw new 
InvalidOperationException();

                
currentState.IsCriticalSection false;
                
shouldAbort currentState.AbortState == AbortState.ShouldAbort;
            }
            if (
shouldAbort)
            {
                
// The monitoring task indicated that it wanted to abort our
                // thread while we were in a critical section, so we need to abort
                // ourselves.
                
Thread.CurrentThread.Abort(currentState);
            }
        }

        private 
enum AbortState
        
{
            
/// <summary>
            /// Indicates that the monitoring task has not yet done any action.
            /// </summary>
            
None 0,

            
/// <summary>
            /// Indicates that the monitoring task is about to abort the handler
            /// thread.
            /// </summary>
            
IsAborting 1,

            
/// <summary>
            /// Indicates that the monitoring task wanted to abort the handler
            /// thread but the handler thread was in a critical section, and needs
            /// to abort itself when leaving the critical section.
            /// </summary>
            
ShouldAbort 2
        
}

        private class 
HandlerState IDisposable
        
{

            public 
Thread HandlerThread get; }

            public 
SemaphoreSlim WaitSemaphore get; } = new SemaphoreSlim(0);

            public 
bool IsCriticalSection getset; }

            
/// <summary>
            /// Indicates if the handler is already completed (so there's no need
            /// to abort the thread).
            /// This flag is set by the handler thread.
            /// </summary>
            
public bool IsExited getset; }

            
/// <summary>
            /// Indicates that the wait task wanted to abort the handler thread but
            /// the handler thread was in a critical section, and needs to abort
            /// itself when leaving the critical section.
            /// This flag is set by the wait task.
            /// </summary>
            
public AbortState AbortState getset; }


            public 
HandlerState(Thread handlerThread)
            {
                
this.HandlerThread handlerThread;
            }

            ~
HandlerState()
            {
                
Dispose(false);
            }

            public 
void Dispose()
            {
                
Dispose(true);
                
GC.SuppressFinalize(this);
            }

            protected 
void Dispose(bool disposing)
            {
                if (
disposing)
                {
                    
WaitSemaphore.Dispose();
                }
            }
        }
    }




原来执行javascpript的方式如下:
[复制到剪贴板]

var engine = new ScriptEngine();
var 
compiledScript engine.Compile(new StringScriptSource(
    
"while (true) ;"));
compiledScript.Execute(); 



改进后新的执行javascpript的方式如下:


[复制到剪贴板]

var timeoutHelper = new ScriptTimeoutHelper();
var 
engine = new ScriptEngine();
var 
compiledScript engine.Compile(new StringScriptSource(
    
"while (true) ;"));
try{
timeoutHelper.RunWithTimeout(() => compiledScript.Execute(), 1000);
}
catch(
TimeoutException ex){
//这里表示执行超时
}
 




注意事项:


我们使用useScriptEngine.Compile()首先编译脚本,然后将超时仅应用于实际执行脚本。
上面的调用代码设置的超时时间是1秒,如果JavaScript未在1秒钟内完成,我们将捕获到TimeoutException异常。


注:在发生TimeoutException以后,我们不应该继续使用ScriptEngine及其关联的对象实例,因为它们可能处于不一致/不可用状态。而是创建一个新的ScriptEngine实例以运行更多脚本文件。

引用通告地址 (0):
复制引用地址https://www.rickw.cn/trackback/283
复制引用地址https://www.rickw.cn/trackback/283/GBK
[ 分类:C# .Net编程  | 查看:802 ]

暂时没有评论,快来发表一个评论吧。
发表评论
作者:   用户:[访客] 
评论:

表  情
禁止表情 | 禁止UBB | 禁止图片 | 识别链接
对不起,你没有权限上传附件!
验证:
 
PoweredBy R-Blog V1.00 © 2004-2024 WWW.RICKW.CN, Processed in second(s) , 7 queries    京ICP备17058477号-5