Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

exception after closing the window #459

Open
linwaytin opened this issue Oct 10, 2019 · 6 comments
Open

exception after closing the window #459

linwaytin opened this issue Oct 10, 2019 · 6 comments

Comments

@linwaytin
Copy link

linwaytin commented Oct 10, 2019

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
Copy link

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
Copy link
Author

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 stevengj changed the title julia crashes after closing the window exception after closing the window Jan 23, 2020
@stevengj
Copy link
Member

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
Copy link
Member

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
Copy link
Member

stevengj commented Jan 23, 2020

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 is None, then signal.signal(signal.SIGINT, None) shouldn't throw an error if the handler is not being changed, and signal.signal(signal.SIGINT, signal.SIG_DFL) shouldn't change the signal handler. Filed https://bugs.python.org/issue39438

  • CPython should document that if getsignal returns None, 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 returns None. (submitted PR: don't override non-Python signal handlers matplotlib/matplotlib#16311)

  • As a temporary workaround, we could monkey-patch Matplotlib to avoid changing the signal handler in the qt5 backend.

@stevengj
Copy link
Member

stevengj commented Jan 25, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants