PyPlot.jl
PyPlot.jl copied to clipboard
exception after closing the window
The script is like this:
# plot.jl
using PyPlot
plot([2,4], [3,8])
show()
and then I run the script in a terminal. It shows a window displaying the plot. Everything is fine until the window is closed.
The error message is the following:
ERROR: LoadError: PyError ($(Expr(:escape, :(ccall(#= /home/mine/.julia/packages/PyCall/ttONZ/src/pyfncall.jl:44 =# @pysym(:PyObject_Call), PyPtr, (PyPtr, PyPtr, PyPtr), o, pyargsptr, kw))))) <class 'TypeError'>
TypeError('signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object')
File "/usr/lib/python3.7/site-packages/matplotlib/pyplot.py", line 269, in show
return _show(*args, **kw)
File "/usr/lib/python3.7/site-packages/matplotlib/cbook/deprecation.py", line 413, in wrapper
return func(*args, **kwargs)
File "/usr/lib/python3.7/site-packages/matplotlib/backend_bases.py", line 3302, in show
cls.mainloop()
File "/usr/lib/python3.7/site-packages/matplotlib/backends/backend_qt5.py", line 1099, in mainloop
signal.signal(signal.SIGINT, old_signal)
File "/usr/lib/python3.7/signal.py", line 47, in signal
handler = _signal.signal(_enum_to_int(signalnum), _enum_to_int(handler))
Stacktrace:
[1] pyerr_check at /home/mine/.julia/packages/PyCall/ttONZ/src/exception.jl:60 [inlined]
[2] pyerr_check at /home/mine/.julia/packages/PyCall/ttONZ/src/exception.jl:64 [inlined]
[3] macro expansion at /home/mine/.julia/packages/PyCall/ttONZ/src/exception.jl:84 [inlined]
[4] __pycall!(::PyCall.PyObject, ::Ptr{PyCall.PyObject_struct}, ::PyCall.PyObject, ::Ptr{Nothing}) at /home/mine/.julia/packages/PyCall/ttONZ/src/pyfncall.jl:44
[5] _pycall!(::PyCall.PyObject, ::PyCall.PyObject, ::Tuple{}, ::Int64, ::Ptr{Nothing}) at /home/mine/.julia/packages/PyCall/ttONZ/src/pyfncall.jl:29
[6] #pycall#109 at /home/mine/.julia/packages/PyCall/ttONZ/src/pyfncall.jl:11 [inlined]
[7] pycall at /home/mine/.julia/packages/PyCall/ttONZ/src/pyfncall.jl:83 [inlined]
[8] #show#148 at /home/mine/.julia/packages/PyPlot/4wzW1/src/PyPlot.jl:183 [inlined]
[9] show() at /home/mine/.julia/packages/PyPlot/4wzW1/src/PyPlot.jl:183
[10] top-level scope at /home/mine/Work/TDGL/src/plot.jl:5
in expression starting at /home/mine/Work/TDGL/src/plot.jl:5
My julia version is 1.2.0.
Did you figure out what caused this? I'm just using Python to plot things with Matplotlib and I noticed that when I closed the window, I got the same error. I suspect it is some update in the QT library? It has only started happening after I updated. Thanks!
I have no idea but I do think it's related to the graphical backend. Did you have the error in running the script or in the REPL environment?
I can reproduce the problem. It's not a "crash", just an exception. You can reproduce it interactively with:
using PyPlot
matplotlib."interactive"(false)
plot([2,4], [3,8])
show()
which throws the error after closing the window. Interestingly, if you call the plot
and show
functions a second time in the same session, there is no error.
The exception is triggered by this line in the qt5 backend, and I can reproduce it as follows:
using PyCall
signal = pyimport("signal")
old_signal = pycall(signal."getsignal", PyObject, signal."SIGINT")
pycall(signal."signal", PyObject, signal."SIGINT", old_signal)
The problem seems to be that old_signal
is PyObject None
, probably because no signal handler has been set for embedded Python usage, but None
is not accepted as an argument to signal.signal
. (This seems like a bug in Python? signal
should accept the return value of getsignal
…)
Meanwhile, we can probably work around it by setting the handler to SIG_DFL
, e.g. doing this before plotting eliminates the exception:
let signal = PyPlot.PyCall.pyimport("signal")
signal."signal"(signal."SIGINT", signal."SIG_DFL")
end
but I'm not sure if that's the correct behavior in general… the interaction of Python signal handling with Julia signal handling is a little murky.
Basically, the problem is that Julia installs its own SIGINT
handler, and the Python signals module doesn't know how to save/restore signal handlers when that happens. The following stand-alone embedded Python code in C reproduces the same TypeError: signal handler must be ...
problem:
#include <Python.h>
#include <signal.h>
#include <stdio.h>
void myhandler(int sig) { printf("got signal %d\n", sig); }
int main(void)
{
signal(SIGINT, myhandler);
Py_InitializeEx(0);
PyRun_SimpleString("import signal\n"
"old_signal = signal.getsignal(signal.SIGINT)\n"
"signal.signal(signal.SIGINT, old_signal)\n"
"print(old_signal)\n");
Py_Finalize();
return 0;
}
This will make it difficult to use PyCall with any module that tries to muck with the signal handlers.
Several things that could be done here:
-
CPython should arguably support this better — if
old_signal
isNone
, thensignal.signal(signal.SIGINT, None)
shouldn't throw an error if the handler is not being changed, andsignal.signal(signal.SIGINT, signal.SIG_DFL)
shouldn't change the signal handler. Filed https://bugs.python.org/issue39438 -
CPython should document that if
getsignal
returnsNone
, then you probably shouldn't try to change the signal handler because there is no way to restore the old handler. -
Matplotlib should not attempt to change the signal handler if
getsignal
returnsNone
. (submitted PR: matplotlib/matplotlib#16311) -
As a temporary workaround, we could monkey-patch Matplotlib to avoid changing the signal handler in the qt5 backend.
Should be fixed in matplotlib 3.1.3 (the next release).