JuliaPy / PyPlot.jl

Plotting for Julia based on matplotlib.pyplot
https://github.com/JuliaPy/PyPlot.jl
MIT License
475 stars 87 forks source link

exception after closing the window #459

Open linwaytin opened 4 years ago

linwaytin commented 4 years ago

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.

timjim333 commented 4 years ago

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!

linwaytin commented 4 years ago

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?

stevengj commented 4 years ago

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.

stevengj commented 4 years ago

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.

stevengj commented 4 years ago

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:

stevengj commented 4 years ago

Should be fixed in matplotlib 3.1.3 (the next release).