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 threading
|
||||
import signal
|
||||
import cutter_internal
|
||||
from ipykernel.kernelapp import IPKernelApp
|
||||
from ipykernel.ipkernel import IPythonKernel
|
||||
|
||||
|
||||
# TODO: Make this behave like a Popen instance and pipe it to IPyKernelInterfaceJupyter!
|
||||
class IPyKernelInterfaceKernel:
|
||||
def __init__(self, thread, app):
|
||||
self._thread = thread
|
||||
self._app = app
|
||||
|
||||
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):
|
||||
return None
|
||||
if self._thread.is_alive():
|
||||
return None
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
class CutterIPythonKernel(IPythonKernel):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.interruptable = False
|
||||
|
||||
def pre_handler_hook(self):
|
||||
self.interruptable = True
|
||||
pass
|
||||
|
||||
def post_handler_hook(self):
|
||||
self.interruptable = False
|
||||
pass
|
||||
|
||||
|
||||
class CutterIPKernelApp(IPKernelApp):
|
||||
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
|
||||
|
||||
|
||||
def launch_ipykernel(argv):
|
||||
app = CutterIPKernelApp.instance()
|
||||
|
||||
def run_kernel():
|
||||
app = CutterIPKernelApp.instance()
|
||||
app.kernel_class = CutterIPythonKernel
|
||||
#app.log_level = logging.DEBUG
|
||||
app.log_level = logging.DEBUG
|
||||
app.initialize(argv[3:])
|
||||
app.start()
|
||||
|
||||
thread = threading.Thread(target=run_kernel)
|
||||
thread.start()
|
||||
|
||||
return IPyKernelInterfaceKernel()
|
||||
return IPyKernelInterfaceKernel(thread, app)
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
|
||||
import signal
|
||||
import time
|
||||
from jupyter_client.ioloop import IOLoopKernelManager
|
||||
from notebook.notebookapp import *
|
||||
import signal
|
||||
import cutter_internal
|
||||
|
||||
|
||||
@ -10,6 +11,7 @@ class IPyKernelInterfaceJupyter:
|
||||
self._id = id
|
||||
|
||||
def send_signal(self, signum):
|
||||
print("sending signal " + str(signum) + " to kernel")
|
||||
cutter_internal.kernel_interface_send_signal(self._id, signum)
|
||||
|
||||
def kill(self):
|
||||
@ -22,17 +24,23 @@ class IPyKernelInterfaceJupyter:
|
||||
return cutter_internal.kernel_interface_poll(self._id)
|
||||
|
||||
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
|
||||
|
||||
|
||||
class CutterInternalIPyKernelManager(IOLoopKernelManager):
|
||||
def start_kernel(self, **kw):
|
||||
# write connection file / get default ports
|
||||
self.write_connection_file()
|
||||
|
||||
# save kwargs for use in restart
|
||||
self._launch_args = kw.copy()
|
||||
# build the Popen cmd
|
||||
|
||||
extra_arguments = kw.pop('extra_arguments', [])
|
||||
kernel_cmd = self.format_kernel_cmd(extra_arguments=extra_arguments)
|
||||
env = kw.pop('env', os.environ).copy()
|
||||
@ -45,9 +53,6 @@ class CutterInternalIPyKernelManager(IOLoopKernelManager):
|
||||
env.update(self.kernel_spec.env or {})
|
||||
|
||||
# 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)
|
||||
self.kernel = IPyKernelInterfaceJupyter(id)
|
||||
# self._launch_kernel(kernel_cmd, env=env, **kw)
|
||||
|
@ -96,6 +96,11 @@ void JupyterConnection::start()
|
||||
if (!cutterJupyterModule)
|
||||
{
|
||||
createCutterJupyterModule();
|
||||
|
||||
if(!cutterJupyterModule)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PyEval_RestoreThread(pyThreadState);
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include <Python.h>
|
||||
|
||||
#include <QFile>
|
||||
#include <csignal>
|
||||
|
||||
#include "cutter.h"
|
||||
#include "NestedIPyKernel.h"
|
||||
@ -66,7 +67,11 @@ NestedIPyKernel::~NestedIPyKernel()
|
||||
void NestedIPyKernel::sendSignal(long signum)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
@ -75,9 +80,16 @@ QVariant NestedIPyKernel::poll()
|
||||
QVariant ret;
|
||||
auto parentThreadState = PyThreadState_Swap(threadState);
|
||||
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);
|
||||
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[] = {
|
||||
{"launch_ipykernel", (PyCFunction)api_internal_launch_ipykernel, METH_VARARGS | METH_KEYWORDS,
|
||||
"Launch an IPython Kernel in a subinterpreter"},
|
||||
{"kernel_interface_send_signal", (PyCFunction)api_internal_kernel_interface_send_signal, 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}
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user