From 693fc1eb1fc76d7bd65ab508a6f85d029c8308d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= <info@florianmaerkl.de> Date: Fri, 23 Feb 2018 13:04:53 +0100 Subject: [PATCH] IPyKernel signal and poll --- src/python/cutter_ipykernel.py | 4 +-- src/python/cutter_jupyter.py | 35 +++++++++++++------------ src/utils/JupyterConnection.cpp | 7 ++++- src/utils/NestedIPyKernel.cpp | 20 ++++++++++++--- src/utils/NestedIPyKernel.h | 3 ++- src/utils/PythonAPI.cpp | 45 +++++++++++++++++++++++++++++---- 6 files changed, 84 insertions(+), 30 deletions(-) diff --git a/src/python/cutter_ipykernel.py b/src/python/cutter_ipykernel.py index 9dcf0ad0..242feef3 100644 --- a/src/python/cutter_ipykernel.py +++ b/src/python/cutter_ipykernel.py @@ -7,8 +7,8 @@ from ipykernel.ipkernel import IPythonKernel # TODO: Make this behave like a Popen instance and pipe it to IPyKernelInterfaceJupyter! class IPyKernelInterfaceKernel: - def kill(self): - print("No!! Not into the pit! It burns!!!") + def send_signal(self, signum): + pass def poll(self): return None diff --git a/src/python/cutter_jupyter.py b/src/python/cutter_jupyter.py index f261b436..967b452e 100644 --- a/src/python/cutter_jupyter.py +++ b/src/python/cutter_jupyter.py @@ -1,34 +1,32 @@ from jupyter_client.ioloop import IOLoopKernelManager from notebook.notebookapp import * +import signal +import cutter_internal -# TODO: this must behave like a Popen instance and pipe to IPyKernelInterfaceKernel! class IPyKernelInterfaceJupyter: def __init__(self, id): self._id = id + def send_signal(self, signum): + cutter_internal.kernel_interface_send_signal(self._id, signum) + def kill(self): - print("DIE!! " + str(self._id)) + self.send_signal(signal.SIGKILL) + + def terminate(self): + self.send_signal(signal.SIGTERM) def poll(self): - return None + return cutter_internal.kernel_interface_poll(self._id) + + def wait(self, timeout=None): + pass class CutterInternalIPyKernelManager(IOLoopKernelManager): def start_kernel(self, **kw): - """Starts a kernel on this host in a separate process. - - If random ports (port=0) are being used, this method must be called - before the channels are created. - - Parameters - ---------- - `**kw` : optional - keyword arguments that are passed down to build the kernel_cmd - and launching the kernel (e.g. Popen kwargs). - """ - # write connection file / get default ports self.write_connection_file() @@ -50,15 +48,16 @@ class CutterInternalIPyKernelManager(IOLoopKernelManager): self.log.debug("Starting kernel: %s", kernel_cmd) # TODO: kernel_cmd including python executable and so on is currently used for argv. Make a clean version! - import cutter_internal id = cutter_internal.launch_ipykernel(kernel_cmd, env=env, **kw) self.kernel = IPyKernelInterfaceJupyter(id) - # self._launch_kernel(kernel_cmd, env=env, - # **kw) + # self._launch_kernel(kernel_cmd, env=env, **kw) self.start_restarter() self._connect_control_socket() + def signal_kernel(self, signum): + self.kernel.send_signal(signum) + def kernel_manager_factory(kernel_name, **kwargs): if kernel_name in {"python", "python2", "python3"}: diff --git a/src/utils/JupyterConnection.cpp b/src/utils/JupyterConnection.cpp index e727b5dc..5d3a67c8 100644 --- a/src/utils/JupyterConnection.cpp +++ b/src/utils/JupyterConnection.cpp @@ -144,5 +144,10 @@ long JupyterConnection::startNestedIPyKernel(const QStringList &argv) NestedIPyKernel *JupyterConnection::getNestedIPyKernel(long id) { - return kernels[id]; + auto it = kernels.find(id); + if(it == kernels.end()) + { + return nullptr; + } + return *it; } diff --git a/src/utils/NestedIPyKernel.cpp b/src/utils/NestedIPyKernel.cpp index cb97ce00..9e2bc9b5 100644 --- a/src/utils/NestedIPyKernel.cpp +++ b/src/utils/NestedIPyKernel.cpp @@ -45,6 +45,8 @@ NestedIPyKernel *NestedIPyKernel::start(const QStringList &argv) NestedIPyKernel::NestedIPyKernel(PyObject *cutterIPykernelModule, const QStringList &argv) { + threadState = PyThreadState_Get(); + auto launchFunc = PyObject_GetAttrString(cutterIPykernelModule, "launch_ipykernel"); PyObject *argvListObject = PyList_New(argv.size()); @@ -59,12 +61,24 @@ NestedIPyKernel::NestedIPyKernel(PyObject *cutterIPykernelModule, const QStringL NestedIPyKernel::~NestedIPyKernel() { - } -void NestedIPyKernel::kill() +void NestedIPyKernel::sendSignal(long signum) { auto parentThreadState = PyThreadState_Swap(threadState); - PyObject_CallMethod(kernel, "kill", nullptr); + PyObject_CallMethod(kernel, "send_signal", "l", signum); PyThreadState_Swap(parentThreadState); } + +QVariant NestedIPyKernel::poll() +{ + QVariant ret; + auto parentThreadState = PyThreadState_Swap(threadState); + PyObject *pyRet = PyObject_CallMethod(kernel, "poll", nullptr); + if(PyLong_Check(pyRet)) + { + ret = (qlonglong)PyLong_AsLong(pyRet); + } + PyThreadState_Swap(parentThreadState); + return ret; +} \ No newline at end of file diff --git a/src/utils/NestedIPyKernel.h b/src/utils/NestedIPyKernel.h index 316b6bad..12751f87 100644 --- a/src/utils/NestedIPyKernel.h +++ b/src/utils/NestedIPyKernel.h @@ -17,7 +17,8 @@ public: static NestedIPyKernel *start(const QStringList &argv); ~NestedIPyKernel(); - void kill(); + void sendSignal(long signum); + QVariant poll(); private: NestedIPyKernel(PyObject *cutterIPykernelModule, const QStringList &argv); diff --git a/src/utils/PythonAPI.cpp b/src/utils/PythonAPI.cpp index 3e1cd7c5..d9f0b708 100644 --- a/src/utils/PythonAPI.cpp +++ b/src/utils/PythonAPI.cpp @@ -74,25 +74,60 @@ PyObject *api_internal_launch_ipykernel(PyObject *self, PyObject *args, PyObject return PyLong_FromLong(id); } -PyObject *api_internal_kernel_interface_kill(PyObject *, PyObject *args) +PyObject *api_internal_kernel_interface_send_signal(PyObject *, PyObject *args) +{ + long id; + long signum; + + if (!PyArg_ParseTuple(args, "ll", &id, &signum)) + { + qWarning() << "Invalid args passed to api_internal_kernel_interface_send_signal()."; + return nullptr; + } + + NestedIPyKernel *kernel = Jupyter()->getNestedIPyKernel(id); + if(kernel) + { + kernel->sendSignal(signum); + } + + Py_RETURN_NONE; +} + +PyObject *api_internal_kernel_interface_poll(PyObject *, PyObject *args) { long id; if (!PyArg_ParseTuple(args, "l", &id)) { - qWarning() << "Invalid args passed to api_internal_kernel_interface_kill()."; + qWarning() << "Invalid args passed to api_internal_kernel_interface_poll()."; return nullptr; } - Jupyter()->getNestedIPyKernel(id)->kill(); + NestedIPyKernel *kernel = Jupyter()->getNestedIPyKernel(id); + if(!kernel) + { + return PyLong_FromLong(0); + } - Py_RETURN_NONE; + QVariant v = kernel->poll(); + bool ok; + auto ret = static_cast<long>(v.toLongLong(&ok)); + if(ok) + { + return PyLong_FromLong(ret); + } + else + { + Py_RETURN_NONE; + } } PyMethodDef CutterInternalMethods[] = { {"launch_ipykernel", (PyCFunction)api_internal_launch_ipykernel, METH_VARARGS | METH_KEYWORDS, "Launch an IPython Kernel in a subinterpreter"}, - {"kernel_interface_kill", (PyCFunction)api_internal_kernel_interface_kill, METH_VARARGS, ""}, + {"kernel_interface_send_signal", (PyCFunction)api_internal_kernel_interface_send_signal, METH_VARARGS, ""}, + {"kernel_interface_poll", (PyCFunction)api_internal_kernel_interface_poll, METH_VARARGS, ""}, {NULL, NULL, 0, NULL} };