Occasional errors while serving via Django app "'NoneType' object has no attribute 'dpi_scale_trans'" (or 'canvas')

Hi,

we generate some plots on the fly for some of the pages on our platform. The site is written in django and the gunicorn server use gthreads (I suspect it might be relevant). The code generating the graph is something like this:

def image_view(request):
    figure = get_figure(request.data)
    response = http.HttpResponse(content_type="image/png")
    figure.savefig(response, format="png", bbox_inches="tight")
    plt.close(figure)
    return response

and the get_figure is the “usual” graph of something like:

matplotlib.use("AGG")

def get_figure(data):
  figure, axis = plt.subplots(figsize=(6, 2.2), dpi=200, num=getrandbits(32))
  ...
  return figure

and nothing “weird” is happening inside it but regular matplotlib stuff (not that I don’t want to share it, it’s just not relevant I believe - it’s normal plotting and it works in 99% of cases). It works for all our manual tests (including stress tests) and answers correctly to tens of thousands requests, but from time to time, it throws these errors (which we cannot replicate on the very same data):

AttributeError: 'NoneType' object has no attribute 'canvas'
'NoneType' object has no attribute 'dpi_scale_trans'

We suspect it might be something caused by threading (but don’t know what or how to prevent that from happening). Any ideas what we could try or what we are doing wrong?

The stacktraces:

If you are already using the “explict” API (and not using plt.XYZ in the rest of the code I suggest you change to

from matplotlib.figure import Figure

def get_figure(data):
    figure = Figure(figsize=(6, 2.2), dpi=200)
    axis = figure.subplots()   # really hard not to rename this to ax
    ...
    return figure 

Which will not tell pyplot about the Figure so you can just let it go out of scope and get garbage collected like any other Python object.

1 Like

I see, good idea. We are using explicit API up to a few instances. I have now changed all of them (but plt.close, for which I cannot find non-pyplot equivalent and rcParams for font settings) to use explicit API. Will try! Thank you

If you are not using pyplot to create the Figures , you also do not need it to close the figures so there in so direct replacement.

I also hope you are using mpl.use('agg') or the equivalent already as well or you are creating a bunch of GUI objects for no reason.

rcParams actually live at the top level (matplotlib.rcParams) and is imported to plt.

Given that this is running in a webserver, you have very tight control of the life cycle, and there are threads I also suggest doing

import sys
sys.modules['matplotlib.pyplot'] = None

which will prevent pyplot (and it’s global state) from being imported!

Thanks for the tips! Applied!

I also hope you are using mpl.use(‘agg’) or the equivalent already as well or you are creating a bunch of GUI objects for no reason.

I used that up until now, but now switched to just using from matplotlib.backends.backend_agg import FigureCanvasAgg (new for every request/figure). Is that OK?

I think so (but depends one exactly what you are doing).

Hey. I confirm that removing all plt code helped!

1 Like