mirror of
https://github.com/rizinorg/cutter.git
synced 2024-12-18 19:06:10 +00:00
Support interrupting nested IPyKernel
This commit is contained in:
parent
693fc1eb1f
commit
ec55e40d5e
@ -1,42 +1,70 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
import signal
|
||||||
|
import cutter_internal
|
||||||
from ipykernel.kernelapp import IPKernelApp
|
from ipykernel.kernelapp import IPKernelApp
|
||||||
from ipykernel.ipkernel import IPythonKernel
|
from ipykernel.ipkernel import IPythonKernel
|
||||||
|
|
||||||
|
|
||||||
# TODO: Make this behave like a Popen instance and pipe it to IPyKernelInterfaceJupyter!
|
|
||||||
class IPyKernelInterfaceKernel:
|
class IPyKernelInterfaceKernel:
|
||||||
|
def __init__(self, thread, app):
|
||||||
|
self._thread = thread
|
||||||
|
self._app = app
|
||||||
|
|
||||||
def send_signal(self, signum):
|
def send_signal(self, signum):
|
||||||
pass
|
if not self._thread.is_alive():
|
||||||
|
return
|
||||||
|
|
||||||
|
if signum == signal.SIGKILL or signum == signal.SIGTERM:
|
||||||
|
self._app.io_loop.stop()
|
||||||
|
elif signum == signal.SIGINT and self._app.kernel.interruptable:
|
||||||
|
self._app.log.debug("Sending KeyboardInterrupt to ioloop thread.")
|
||||||
|
cutter_internal.thread_set_async_exc(self._thread.ident, KeyboardInterrupt())
|
||||||
|
|
||||||
def poll(self):
|
def poll(self):
|
||||||
return None
|
if self._thread.is_alive():
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
class CutterIPythonKernel(IPythonKernel):
|
class CutterIPythonKernel(IPythonKernel):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.interruptable = False
|
||||||
|
|
||||||
def pre_handler_hook(self):
|
def pre_handler_hook(self):
|
||||||
|
self.interruptable = True
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def post_handler_hook(self):
|
def post_handler_hook(self):
|
||||||
|
self.interruptable = False
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CutterIPKernelApp(IPKernelApp):
|
class CutterIPKernelApp(IPKernelApp):
|
||||||
def init_signal(self):
|
def init_signal(self):
|
||||||
|
# This would call signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||||
|
# Not needed in supinterpreter.
|
||||||
|
pass
|
||||||
|
|
||||||
|
def log_connection_info(self):
|
||||||
|
# Just skip this. It would only pollute Cutter's output.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def launch_ipykernel(argv):
|
def launch_ipykernel(argv):
|
||||||
|
app = CutterIPKernelApp.instance()
|
||||||
|
|
||||||
def run_kernel():
|
def run_kernel():
|
||||||
app = CutterIPKernelApp.instance()
|
|
||||||
app.kernel_class = CutterIPythonKernel
|
app.kernel_class = CutterIPythonKernel
|
||||||
#app.log_level = logging.DEBUG
|
app.log_level = logging.DEBUG
|
||||||
app.initialize(argv[3:])
|
app.initialize(argv[3:])
|
||||||
app.start()
|
app.start()
|
||||||
|
|
||||||
thread = threading.Thread(target=run_kernel)
|
thread = threading.Thread(target=run_kernel)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
return IPyKernelInterfaceKernel()
|
return IPyKernelInterfaceKernel(thread, app)
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
|
||||||
|
import signal
|
||||||
|
import time
|
||||||
from jupyter_client.ioloop import IOLoopKernelManager
|
from jupyter_client.ioloop import IOLoopKernelManager
|
||||||
from notebook.notebookapp import *
|
from notebook.notebookapp import *
|
||||||
import signal
|
|
||||||
import cutter_internal
|
import cutter_internal
|
||||||
|
|
||||||
|
|
||||||
@ -10,6 +11,7 @@ class IPyKernelInterfaceJupyter:
|
|||||||
self._id = id
|
self._id = id
|
||||||
|
|
||||||
def send_signal(self, signum):
|
def send_signal(self, signum):
|
||||||
|
print("sending signal " + str(signum) + " to kernel")
|
||||||
cutter_internal.kernel_interface_send_signal(self._id, signum)
|
cutter_internal.kernel_interface_send_signal(self._id, signum)
|
||||||
|
|
||||||
def kill(self):
|
def kill(self):
|
||||||
@ -22,17 +24,23 @@ class IPyKernelInterfaceJupyter:
|
|||||||
return cutter_internal.kernel_interface_poll(self._id)
|
return cutter_internal.kernel_interface_poll(self._id)
|
||||||
|
|
||||||
def wait(self, timeout=None):
|
def wait(self, timeout=None):
|
||||||
|
if timeout is not None:
|
||||||
|
start_time = time.process_time()
|
||||||
|
else:
|
||||||
|
start_time = None
|
||||||
|
while timeout is None or time.process_time() - start_time < timeout:
|
||||||
|
if self.poll() is not None:
|
||||||
|
return
|
||||||
|
time.sleep(0.1)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CutterInternalIPyKernelManager(IOLoopKernelManager):
|
class CutterInternalIPyKernelManager(IOLoopKernelManager):
|
||||||
def start_kernel(self, **kw):
|
def start_kernel(self, **kw):
|
||||||
# write connection file / get default ports
|
|
||||||
self.write_connection_file()
|
self.write_connection_file()
|
||||||
|
|
||||||
# save kwargs for use in restart
|
|
||||||
self._launch_args = kw.copy()
|
self._launch_args = kw.copy()
|
||||||
# build the Popen cmd
|
|
||||||
extra_arguments = kw.pop('extra_arguments', [])
|
extra_arguments = kw.pop('extra_arguments', [])
|
||||||
kernel_cmd = self.format_kernel_cmd(extra_arguments=extra_arguments)
|
kernel_cmd = self.format_kernel_cmd(extra_arguments=extra_arguments)
|
||||||
env = kw.pop('env', os.environ).copy()
|
env = kw.pop('env', os.environ).copy()
|
||||||
@ -45,9 +53,6 @@ class CutterInternalIPyKernelManager(IOLoopKernelManager):
|
|||||||
env.update(self.kernel_spec.env or {})
|
env.update(self.kernel_spec.env or {})
|
||||||
|
|
||||||
# launch the kernel subprocess
|
# launch the kernel subprocess
|
||||||
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!
|
|
||||||
id = cutter_internal.launch_ipykernel(kernel_cmd, env=env, **kw)
|
id = cutter_internal.launch_ipykernel(kernel_cmd, env=env, **kw)
|
||||||
self.kernel = IPyKernelInterfaceJupyter(id)
|
self.kernel = IPyKernelInterfaceJupyter(id)
|
||||||
# self._launch_kernel(kernel_cmd, env=env, **kw)
|
# self._launch_kernel(kernel_cmd, env=env, **kw)
|
||||||
|
@ -96,6 +96,11 @@ void JupyterConnection::start()
|
|||||||
if (!cutterJupyterModule)
|
if (!cutterJupyterModule)
|
||||||
{
|
{
|
||||||
createCutterJupyterModule();
|
createCutterJupyterModule();
|
||||||
|
|
||||||
|
if(!cutterJupyterModule)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PyEval_RestoreThread(pyThreadState);
|
PyEval_RestoreThread(pyThreadState);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#include <Python.h>
|
#include <Python.h>
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
#include <csignal>
|
||||||
|
|
||||||
#include "cutter.h"
|
#include "cutter.h"
|
||||||
#include "NestedIPyKernel.h"
|
#include "NestedIPyKernel.h"
|
||||||
@ -66,7 +67,11 @@ NestedIPyKernel::~NestedIPyKernel()
|
|||||||
void NestedIPyKernel::sendSignal(long signum)
|
void NestedIPyKernel::sendSignal(long signum)
|
||||||
{
|
{
|
||||||
auto parentThreadState = PyThreadState_Swap(threadState);
|
auto parentThreadState = PyThreadState_Swap(threadState);
|
||||||
PyObject_CallMethod(kernel, "send_signal", "l", signum);
|
auto ret = PyObject_CallMethod(kernel, "send_signal", "l", signum);
|
||||||
|
if (!ret)
|
||||||
|
{
|
||||||
|
PyErr_Print();
|
||||||
|
}
|
||||||
PyThreadState_Swap(parentThreadState);
|
PyThreadState_Swap(parentThreadState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,9 +80,16 @@ QVariant NestedIPyKernel::poll()
|
|||||||
QVariant ret;
|
QVariant ret;
|
||||||
auto parentThreadState = PyThreadState_Swap(threadState);
|
auto parentThreadState = PyThreadState_Swap(threadState);
|
||||||
PyObject *pyRet = PyObject_CallMethod(kernel, "poll", nullptr);
|
PyObject *pyRet = PyObject_CallMethod(kernel, "poll", nullptr);
|
||||||
if(PyLong_Check(pyRet))
|
if(pyRet)
|
||||||
{
|
{
|
||||||
ret = (qlonglong)PyLong_AsLong(pyRet);
|
if(PyLong_Check(pyRet))
|
||||||
|
{
|
||||||
|
ret = (qlonglong)PyLong_AsLong(pyRet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PyErr_Print();
|
||||||
}
|
}
|
||||||
PyThreadState_Swap(parentThreadState);
|
PyThreadState_Swap(parentThreadState);
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -123,11 +123,30 @@ PyObject *api_internal_kernel_interface_poll(PyObject *, PyObject *args)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject *api_internal_thread_set_async_exc(PyObject *, PyObject *args)
|
||||||
|
{
|
||||||
|
long id;
|
||||||
|
PyObject *exc;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "lO", &id, &exc))
|
||||||
|
{
|
||||||
|
qWarning() << "Invalid args passed to api_internal_set_async_exc().";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("setasyncexc %ld\n", id);
|
||||||
|
int ret = PyThreadState_SetAsyncExc(id, exc);
|
||||||
|
printf("%d threads affected\n", ret);
|
||||||
|
|
||||||
|
return PyLong_FromLong(ret);
|
||||||
|
}
|
||||||
|
|
||||||
PyMethodDef CutterInternalMethods[] = {
|
PyMethodDef CutterInternalMethods[] = {
|
||||||
{"launch_ipykernel", (PyCFunction)api_internal_launch_ipykernel, METH_VARARGS | METH_KEYWORDS,
|
{"launch_ipykernel", (PyCFunction)api_internal_launch_ipykernel, METH_VARARGS | METH_KEYWORDS,
|
||||||
"Launch an IPython Kernel in a subinterpreter"},
|
"Launch an IPython Kernel in a subinterpreter"},
|
||||||
{"kernel_interface_send_signal", (PyCFunction)api_internal_kernel_interface_send_signal, 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, ""},
|
{"kernel_interface_poll", (PyCFunction)api_internal_kernel_interface_poll, METH_VARARGS, ""},
|
||||||
|
{"thread_set_async_exc", (PyCFunction)api_internal_thread_set_async_exc, METH_VARARGS, ""},
|
||||||
{NULL, NULL, 0, NULL}
|
{NULL, NULL, 0, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user