Skip to content
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

Added timeout argument to limit javascript function call time #4

Open
wants to merge 2 commits into
base: master
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
6 changes: 6 additions & 0 deletions PyV8.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,13 @@ def int_or_nul(value):
def frames(self):
return self.parse_stack(self.stackTrace)


class JSTimeoutError(Exception):
pass


_PyV8._JSError._jsclass = JSError
_PyV8._JSTimeoutError._jsclass = JSTimeoutError

JSObject = _PyV8.JSObject
JSNull = _PyV8.JSNull
Expand Down
66 changes: 60 additions & 6 deletions src/Context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@ void CContext::Expose(void)
py::arg("name") = std::string(),
py::arg("line") = -1,
py::arg("col") = -1,
py::arg("precompiled") = py::object()))
py::arg("precompiled") = py::object(),
py::arg("timeout") = py::long_(0)))
.def("eval", &CContext::EvaluateW, (py::arg("source"),
py::arg("name") = std::wstring(),
py::arg("line") = -1,
py::arg("col") = -1,
py::arg("precompiled") = py::object()))
py::arg("precompiled") = py::object(),
py::arg("timeout") = py::long_(0)))

.def("enter", &CContext::Enter, "Enter this context. "
"After entering a context, all code compiled and "
Expand Down Expand Up @@ -205,26 +207,78 @@ py::object CContext::GetCalling(void)
py::object(py::handle<>(boost::python::converter::shared_ptr_to_python<CContext>(CContextPtr(new CContext(calling)))));
}

static bool EvaluationTimeout = false;

void TerminateEvaluation(int sig)
{
EvaluationTimeout = true;
v8::V8::TerminateExecution(v8::Isolate::GetCurrent());
}

inline void SetupEvaluationAlarmHook(long timeout)
{
EvaluationTimeout = false;
signal(SIGALRM, TerminateEvaluation);
ualarm((useconds_t)(timeout * 1000), (useconds_t)0);
}

inline void CheckEvaluationAlarm()
{
signal(SIGALRM, SIG_IGN);
ualarm((useconds_t)0, (useconds_t)0);

if (EvaluationTimeout)
{
EvaluationTimeout = false;

throw CJavascriptTimeoutException("Javascript evaluation timeout");
}
}

py::object CContext::Evaluate(const std::string& src,
const std::string name,
int line, int col,
py::object precompiled)
py::object precompiled, long timeout)
{
CEngine engine(v8::Isolate::GetCurrent());

CScriptPtr script = engine.Compile(src, name, line, col, precompiled);

return script->Run();
if (timeout > 0)
{
SetupEvaluationAlarmHook(timeout);
}

py::object result = script->Run();

if (timeout > 0)
{
CheckEvaluationAlarm();
}

return result;
}

py::object CContext::EvaluateW(const std::wstring& src,
const std::wstring name,
int line, int col,
py::object precompiled)
py::object precompiled, long timeout)
{
CEngine engine(v8::Isolate::GetCurrent());

CScriptPtr script = engine.CompileW(src, name, line, col, precompiled);

return script->Run();
if (timeout > 0)
{
SetupEvaluationAlarmHook(timeout);
}

py::object result = script->Run();

if (timeout > 0)
{
CheckEvaluationAlarm();
}

return result;
}
4 changes: 2 additions & 2 deletions src/Context.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ class CContext
bool HasOutOfMemoryException(void) { v8::HandleScope handle_scope(v8::Isolate::GetCurrent()); return Handle()->HasOutOfMemoryException(); }

py::object Evaluate(const std::string& src, const std::string name = std::string(),
int line = -1, int col = -1, py::object precompiled = py::object());
int line = -1, int col = -1, py::object precompiled = py::object(), long timeout = 0);
py::object EvaluateW(const std::wstring& src, const std::wstring name = std::wstring(),
int line = -1, int col = -1, py::object precompiled = py::object());
int line = -1, int col = -1, py::object precompiled = py::object(), long timeout = 0);

static py::object GetEntered(void);
static py::object GetCurrent(void);
Expand Down
23 changes: 23 additions & 0 deletions src/Exception.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ void CJavascriptException::Expose(void)
ExceptionTranslator::Construct, py::type_id<CJavascriptException>());
}

void CJavascriptTimeoutException::Expose(void)
{
py::class_<CJavascriptTimeoutException >("_JSTimeoutError", py::no_init);

py::register_exception_translator<CJavascriptTimeoutException>(ExceptionTranslator::TranslateTimeout);
}

CJavascriptStackTracePtr CJavascriptStackTrace::GetCurrentStackTrace(
v8::Isolate *isolate, int frame_limit, v8::StackTrace::StackTraceOptions options)
{
Expand Down Expand Up @@ -392,6 +399,22 @@ void ExceptionTranslator::Translate(CJavascriptException const& ex)
}
}

void ExceptionTranslator::TranslateTimeout(CJavascriptTimeoutException const& ex)
{
CPythonGIL python_gil;

// Boost::Python doesn't support inherite from Python class,
// so, just use some workaround to throw our custom exception
//
// http://www.language-binding.net/pyplusplus/troubleshooting_guide/exceptions/exceptions.html

py::object impl(ex);
py::object clazz = impl.attr("_jsclass");
py::object err = clazz(impl);

::PyErr_SetObject(clazz.ptr(), py::incref(err.ptr()));
}

void *ExceptionTranslator::Convertible(PyObject* obj)
{
CPythonGIL python_gil;
Expand Down
20 changes: 20 additions & 0 deletions src/Exception.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
#define END_HANDLE_JAVASCRIPT_EXCEPTION if (try_catch.HasCaught()) CJavascriptException::ThrowIf(v8::Isolate::GetCurrent(), try_catch);

class CJavascriptException;
class CJavascriptTimeoutException;

struct ExceptionTranslator
{
static void Translate(CJavascriptException const& ex);
static void TranslateTimeout(CJavascriptTimeoutException const& ex);

static void *Convertible(PyObject* obj);
static void Construct(PyObject* obj, py::converter::rvalue_from_python_stage1_data* data);
Expand Down Expand Up @@ -178,3 +180,21 @@ class CJavascriptException : public std::runtime_error

static void Expose(void);
};


class CJavascriptTimeoutException : public std::runtime_error
{
public:
CJavascriptTimeoutException(const std::string& msg) :
std::runtime_error(msg)
{
}

CJavascriptTimeoutException(const CJavascriptTimeoutException& ex)
: std::runtime_error(ex.what())
{
}

static void Expose(void);
};

1 change: 1 addition & 0 deletions src/PyV8.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
BOOST_PYTHON_MODULE(_PyV8)
{
CJavascriptException::Expose();
CJavascriptTimeoutException::Expose();
CWrapper::Expose();
CContext::Expose();
#ifdef SUPPORT_AST
Expand Down
77 changes: 69 additions & 8 deletions src/Wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
#include "Context.h"
#include "Utils.h"

#include <unistd.h>
#include <signal.h>

#define TERMINATE_EXECUTION_CHECK(returnValue) \
if(v8::V8::IsExecutionTerminating()) { \
::PyErr_Clear(); \
Expand Down Expand Up @@ -112,12 +115,14 @@ void CWrapper::Expose(void)
.def("apply", &CJavascriptFunction::ApplyPython,
(py::arg("self"),
py::arg("args") = py::list(),
py::arg("kwds") = py::dict()),
py::arg("kwds") = py::dict(),
py::arg("timeout") = py::long_(0)),
"Performs a function call using the parameters.")
.def("invoke", &CJavascriptFunction::Invoke,
(py::arg("args") = py::list(),
py::arg("kwds") = py::dict()),
"Performs a binding method call using the parameters.")
py::arg("kwds") = py::dict(),
py::arg("timeout") = py::long_(0)),
"Performs a binding method call using the parameters.")

.def("setName", &CJavascriptFunction::SetName)

Expand Down Expand Up @@ -1656,22 +1661,78 @@ py::object CJavascriptFunction::CreateWithArgs(CJavascriptFunctionPtr proto, py:
return CJavascriptObject::Wrap(result);
}

py::object CJavascriptFunction::ApplyJavascript(CJavascriptObjectPtr self, py::list args, py::dict kwds)
static bool ApplyJavascriptTimeout = false;

void TerminateExecution(int sig)
{
ApplyJavascriptTimeout = true;
v8::V8::TerminateExecution(v8::Isolate::GetCurrent());
}

inline void SetupAlarmHook(long timeout)
{
ApplyJavascriptTimeout = false;
signal(SIGALRM, TerminateExecution);
ualarm((useconds_t)(timeout * 1000), (useconds_t)0);
}

inline void CheckAlarm()
{
signal(SIGALRM, SIG_IGN);
ualarm((useconds_t)0, (useconds_t)0);

if (ApplyJavascriptTimeout)
{
ApplyJavascriptTimeout = false;

throw CJavascriptTimeoutException("Javascript function call timeout");
}
}

py::object CJavascriptFunction::ApplyJavascript(CJavascriptObjectPtr self, py::list args, py::dict kwds, py::long_ timeout)
{
CHECK_V8_CONTEXT();

v8::HandleScope handle_scope(v8::Isolate::GetCurrent());

return Call(self->Object(), args, kwds);

long timeout_ = py::extract<long>(timeout);

if (timeout_ > 0)
{
SetupAlarmHook(timeout_);
}

py::object result = Call(self->Object(), args, kwds);

if (timeout_ > 0)
{
CheckAlarm();
}

return result;
}

py::object CJavascriptFunction::ApplyPython(py::object self, py::list args, py::dict kwds)
py::object CJavascriptFunction::ApplyPython(py::object self, py::list args, py::dict kwds, py::long_ timeout)
{
CHECK_V8_CONTEXT();

v8::HandleScope handle_scope(v8::Isolate::GetCurrent());

long timeout_ = py::extract<long>(timeout);

if (timeout_ > 0)
{
SetupAlarmHook(timeout_);
}

return Call(CPythonObject::Wrap(self)->ToObject(), args, kwds);
py::object result = Call(CPythonObject::Wrap(self)->ToObject(), args, kwds);

if (timeout_ > 0)
{
CheckAlarm();
}

return result;
}

py::object CJavascriptFunction::Invoke(py::list args, py::dict kwds)
Expand Down
4 changes: 2 additions & 2 deletions src/Wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,8 @@ class CJavascriptFunction : public CJavascriptObject
static py::object CallWithArgs(py::tuple args, py::dict kwds);
static py::object CreateWithArgs(CJavascriptFunctionPtr proto, py::tuple args, py::dict kwds);

py::object ApplyJavascript(CJavascriptObjectPtr self, py::list args, py::dict kwds);
py::object ApplyPython(py::object self, py::list args, py::dict kwds);
py::object ApplyJavascript(CJavascriptObjectPtr self, py::list args, py::dict kwds, py::long_ timeout);
py::object ApplyPython(py::object self, py::list args, py::dict kwds, py::long_ timeout);
py::object Invoke(py::list args, py::dict kwds);

const std::string GetName(void) const;
Expand Down