lava / matplotlib-cpp

Extremely simple yet powerful header-only C++ plotting library built on the popular matplotlib
MIT License
4.38k stars 1.13k forks source link

Crash on program exit #248

Open blakat360 opened 3 years ago

blakat360 commented 3 years ago

The following code segfaults with python 3. It does so on the Py_Finalize call

#include <matplotlibcpp.h>

int main() {
    namespace plt = matplotlibcpp;
    plt::plot({1,3,2,4});
    plt::show();
    plt::close();
    return 0;

The following does not:

#include <matplotlibcpp.h>

int main() {
    namespace plt = matplotlibcpp;
    plt::plot({1,3,2,4});
    plt::show();
    plt::close();
    plt::detail::_interpreter::kill();
    return 0;
}

I'm not plotting in different threads so not sure why this is happening. Perhaps the following will solve this: https://stackoverflow.com/questions/1427002/calling-py-finalize-from-c

JonasBreuling commented 3 years ago

I've encountered the same problem but wasn't able to figure out what goes wrong. But I think you are right that something goes wrong with the call Py_Finalize();.

blakat360 commented 3 years ago

I tried a naive implementation of the above fix and it didnt work :(

Apparently Py_InitThreads is deprecated so no ideas here :( not very familiar with embedding python in c/c++

JonasBreuling commented 3 years ago

That is interesting. I made some investigations and here is what I found out.

The following code crashes with a Segfault

#include <matplotlibcpp.h>
int main()
{
    matplotlibcpp::figure();
    matplotlibcpp::show();
}

but this works fine

#include <matplotlibcpp.h>
int main()
{
    matplotlibcpp::figure();
    matplotlibcpp::show();
    Py_Finalize();
}

I've also implemented a minimal working example of the core features of the above two calls. Everything works fine there...

#include <Python.h>

int main()
{
    Py_Initialize();

    PyObject* matplotlibname = PyUnicode_FromString("matplotlib");
    PyObject* matplotlib = PyImport_Import(matplotlibname);
    Py_DECREF(matplotlibname);

    PyObject* pyplotname = PyUnicode_FromString("matplotlib.pyplot");
    PyObject* pymod = PyImport_Import(pyplotname);
    Py_DECREF(pyplotname);

    PyObject* s_python_function_figure = PyObject_GetAttrString(pymod, "figure");
    PyObject* s_python_function_show = PyObject_GetAttrString(pymod, "show");
    PyObject* empty_tuple = PyTuple_New(0);

    PyObject *fig = PyObject_CallObject(s_python_function_figure, empty_tuple);
    PyObject *res = PyObject_CallObject(s_python_function_show, empty_tuple);

    Py_DECREF(matplotlib);
    Py_DECREF(pymod);
    Py_DECREF(s_python_function_figure);
    Py_DECREF(s_python_function_show);
    Py_DECREF(empty_tuple);
    Py_DECREF(fig);
    Py_DECREF(res);

    Py_Finalize();
}

So somewhere between the above core code and the matplotlibcpp code there is a problem I can't figure out.

blakat360 commented 3 years ago

Is there any way matplotlibcpp or the python interpreter spin up different threads?

JonasBreuling commented 3 years ago

Sorry for the late reply, I missed your message. And I don't know If something goes wrong with the threads.

filiatra commented 3 years ago

Hi, same problem here, adding Py_Finalize(); at the end of the program fixes it.

pascal-mueller commented 3 years ago

This issue might be related to the backend of matplotlib. See #268

amadeus84 commented 2 years ago

@pascal-mueller

It is related, it's explained in the stackoverflow link. Py_Finalize() is called from the _interpreter destructor, which is being called too late, because the interpreter is static. calling kill() or Py_Finalize() before return, in main fixes it. That's with Qt5Agg used by python3. Changing the backend to TkAgg seems to work without issues.

pascal-mueller commented 2 years ago

@amadeus84 Thanks, I remember trying to solve it but couldn't. Can't remember why but also didn't had the time to dig into it too deep. You fix seems easy enough though. I hope it gets merged! It's quit a pain of a bug.