diff --git a/src/duktape/context.c b/src/duktape/context.c index a04ac92a75..8c03c96d9e 100644 --- a/src/duktape/context.c +++ b/src/duktape/context.c @@ -18,6 +18,7 @@ static int DukContext_init(DukContext *self, PyObject *args, PyObject *kw) (void)kw; self->heap_manager = NULL; /* We manage the heap */ + self->py_thread_state = NULL; self->ctx = duk_create_heap_default(); if (!self->ctx) { @@ -87,7 +88,7 @@ static void DukContext_dealloc(DukContext *self) static PyObject *DukContext_eval(DukContext *self, PyObject *args, PyObject *kw) { const char *code; - int noresult = 0; + int noresult = 0, ret = 0; PyObject *result = NULL, *temp = NULL; static char *keywords[] = {"code", "noreturn", NULL}; @@ -97,7 +98,11 @@ static PyObject *DukContext_eval(DukContext *self, PyObject *args, PyObject *kw) } if (temp && PyObject_IsTrue(temp)) noresult = 1; - if (duk_peval_string(self->ctx, code) != 0) { + self->py_thread_state = PyEval_SaveThread(); // Release GIL + ret = duk_peval_string(self->ctx, code); + PyEval_RestoreThread(self->py_thread_state); // Acquire GIL + self->py_thread_state = NULL; + if (ret != 0) { temp = duk_to_python(self->ctx, -1); if (temp) { PyErr_SetObject(JSError, temp); @@ -121,7 +126,7 @@ static PyObject *DukContext_eval(DukContext *self, PyObject *args, PyObject *kw) static PyObject *DukContext_eval_file(DukContext *self, PyObject *args, PyObject *kw) { const char *path; - int noresult = 0; + int noresult = 0, ret = 0; PyObject *result = NULL, *temp = NULL; static char *keywords[] = {"path", "noreturn", NULL}; @@ -131,7 +136,11 @@ static PyObject *DukContext_eval_file(DukContext *self, PyObject *args, PyObject } if (temp && PyObject_IsTrue(temp)) noresult = 1; - if (duk_peval_file(self->ctx, path) != 0) { + self->py_thread_state = PyEval_SaveThread(); // Release GIL + ret = duk_peval_file(self->ctx, path); + PyEval_RestoreThread(self->py_thread_state); // Acquire GIL + self->py_thread_state = NULL; + if (ret != 0) { temp = duk_to_python(self->ctx, -1); if (temp) { PyErr_SetObject(JSError, temp); diff --git a/src/duktape/conversions.c b/src/duktape/conversions.c index bd197f19eb..e273bebefe 100644 --- a/src/duktape/conversions.c +++ b/src/duktape/conversions.c @@ -20,53 +20,95 @@ static int get_repr(PyObject *value, char *buf, int bufsz) { static duk_ret_t python_function_caller(duk_context *ctx) { PyObject *func, *args, *result; + DukContext *dctx; duk_idx_t nargs, i; static char buf1[200], buf2[1024]; + int gil_acquired = 0, ret = 1; + dctx = DukContext_get(ctx); nargs = duk_get_top(ctx); duk_push_current_function(ctx); duk_get_prop_string(ctx, -1, "\xff" "py_object"); func = duk_get_pointer(ctx, -1); + if (dctx->py_thread_state) { + gil_acquired = 1; + PyEval_RestoreThread(dctx->py_thread_state); + dctx->py_thread_state = NULL; + } + args = PyTuple_New(nargs); - if (!args) - return DUK_RET_ALLOC_ERROR; + if (!args) { + ret = DUK_RET_ALLOC_ERROR; + goto error; + } for (i = 0; i < nargs; i++) { PyObject *arg = duk_to_python(ctx, i); - if (arg == NULL) - return DUK_RET_TYPE_ERROR; + if (arg == NULL) { + Py_DECREF(args); + ret = DUK_RET_TYPE_ERROR; + goto error; + } PyTuple_SET_ITEM(args, i, arg); } result = PyObject_Call(func, args, NULL); + Py_DECREF(args); + if (!result) { get_repr(func, buf1, 200); - if (!PyErr_Occurred()) + if (!PyErr_Occurred()) { + if (gil_acquired) { + dctx->py_thread_state = PyEval_SaveThread(); + gil_acquired = 0; + } duk_error(ctx, DUK_ERR_ERROR, "Python function (%s) failed", buf1); + } PyObject *ptype = NULL, *pval = NULL, *tb = NULL; PyErr_Fetch(&ptype, &pval, &tb); if (!get_repr(pval, buf2, 1024)) get_repr(ptype, buf2, 1024); Py_XDECREF(ptype); Py_XDECREF(pval); Py_XDECREF(tb); PyErr_Clear(); /* In case there was an error in get_repr() */ + if (gil_acquired) { + dctx->py_thread_state = PyEval_SaveThread(); + gil_acquired = 0; + } duk_error(ctx, DUK_ERR_ERROR, "Python function (%s) failed with error: %s", buf1, buf2); } python_to_duk(ctx, result); Py_DECREF(result); - return 1; +error: + if (gil_acquired) { + dctx->py_thread_state = PyEval_SaveThread(); + gil_acquired = 0; + } + return ret; } static duk_ret_t python_object_decref(duk_context *ctx) { - int deleted = 0; + int deleted = 0, gil_acquired = 0; + DukContext *dctx = DukContext_get(ctx); + + duk_get_prop_string(ctx, 0, "\xff""deleted"); deleted = duk_to_boolean(ctx, -1); duk_pop(ctx); if (!deleted) { duk_get_prop_string(ctx, 0, "\xff""py_object"); + if (dctx->py_thread_state) { + gil_acquired = 1; + PyEval_RestoreThread(dctx->py_thread_state); + dctx->py_thread_state = NULL; + } Py_XDECREF(duk_get_pointer(ctx, -1)); + if (gil_acquired) { + dctx->py_thread_state = PyEval_SaveThread(); + gil_acquired = 0; + } duk_pop(ctx); // Mark as deleted diff --git a/src/duktape/dukpy.h b/src/duktape/dukpy.h index 4b3dafcdcd..6ec98a340d 100644 --- a/src/duktape/dukpy.h +++ b/src/duktape/dukpy.h @@ -22,6 +22,7 @@ struct DukContext_ { PyObject_HEAD duk_context *ctx; DukContext *heap_manager; + PyThreadState *py_thread_state; }; PyTypeObject DukContext_Type; diff --git a/src/duktape/tests.py b/src/duktape/tests.py index 6fa47fddaf..3c664612bd 100644 --- a/src/duktape/tests.py +++ b/src/duktape/tests.py @@ -1,10 +1,12 @@ import os import sys import unittest +from threading import Thread, Event from duktape import dukpy undefined, JSError, Context = dukpy.undefined, dukpy.JSError, dukpy.Context class ContextTests(unittest.TestCase): + def setUp(self): self.ctx = Context() self.g = self.ctx.g @@ -30,6 +32,7 @@ class ContextTests(unittest.TestCase): class ValueTests(unittest.TestCase): + def setUp(self): self.ctx = Context() self.g = self.ctx.g @@ -106,6 +109,7 @@ class ValueTests(unittest.TestCase): class EvalTests(unittest.TestCase): + def setUp(self): self.ctx = Context() self.g = self.ctx.g @@ -148,6 +152,18 @@ class EvalTests(unittest.TestCase): self.assertEqual('ReferenceError', e.name) self.assertEqual(2, e.lineNumber) + def test_eval_multithreading(self): + ev = Event() + self.ctx.g.func = ev.wait + t = Thread(target=self.ctx.eval, args=('func()',)) + t.daemon = True + t.start() + t.join(0.01) + self.assertTrue(t.is_alive()) + ev.set() + t.join(1) + self.assertFalse(t.is_alive()) + def test_eval_noreturn(self): self.assertIsNone(self.ctx.eval("1+1", noreturn=True)) @@ -166,5 +182,3 @@ class EvalTests(unittest.TestCase): def test_eval_file_noreturn(self): self.assertIsNone(self.ctx.eval_file(self.testfile, noreturn=True)) - -