#20493 closed Bug (fixed)
Add a warning that objects may not be picklable across Django versions
Reported by: | Catalin Iacob | Owned by: | nobody |
---|---|---|---|
Component: | Documentation | Version: | 1.5 |
Severity: | Normal | Keywords: | |
Cc: | iacobcatalin@… | Triage Stage: | Accepted |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
I just upgraded from 1.4 to 1.5 and this took a while to track.
If one has a cache that persists across a Django version change (filesystem cache, memcached that is not restarted), when executing any view decorated with @cache_page unpickling the HttpResponse from the cache leads to strange errors. For example, when upgrading from 1.4 to 1.5 I get:
[24/May/2013 15:53:42] "GET / HTTP/1.1" 200 5 Traceback (most recent call last): File "/usr/lib64/python2.7/wsgiref/handlers.py", line 86, in run self.finish_response() File "/usr/lib64/python2.7/wsgiref/handlers.py", line 129, in finish_response self.close() File "/usr/lib64/python2.7/wsgiref/simple_server.py", line 36, in close SimpleHandler.close(self) File "/usr/lib64/python2.7/wsgiref/handlers.py", line 257, in close self.result.close() File "/home/catalin/hacking/envs/cached-httpresponse-across-versions/lib/python2.7/site-packages/django/http/response.py", line 231, in close for closable in self._closable_objects: AttributeError: 'HttpResponse' object has no attribute '_closable_objects' [24/May/2013 15:53:42] "GET / HTTP/1.1" 500 59 ---------------------------------------- Exception happened during processing of request from ('127.0.0.1', 49852) Traceback (most recent call last): File "/usr/lib64/python2.7/SocketServer.py", line 582, in process_request_thread self.finish_request(request, client_address) File "/usr/lib64/python2.7/SocketServer.py", line 323, in finish_request self.RequestHandlerClass(request, client_address, self) File "/home/catalin/hacking/envs/cached-httpresponse-across-versions/lib/python2.7/site-packages/django/core/servers/basehttp.py", line 150, in __init__ super(WSGIRequestHandler, self).__init__(*args, **kwargs) File "/usr/lib64/python2.7/SocketServer.py", line 638, in __init__ self.handle() File "/usr/lib64/python2.7/wsgiref/simple_server.py", line 124, in handle handler.run(self.server.get_app()) File "/usr/lib64/python2.7/wsgiref/handlers.py", line 89, in run self.handle_error() File "/usr/lib64/python2.7/wsgiref/handlers.py", line 304, in handle_error self.finish_response() File "/usr/lib64/python2.7/wsgiref/handlers.py", line 127, in finish_response self.write(data) File "/usr/lib64/python2.7/wsgiref/handlers.py", line 210, in write self.send_headers() File "/usr/lib64/python2.7/wsgiref/handlers.py", line 267, in send_headers if not self.origin_server or self.client_is_modern(): File "/usr/lib64/python2.7/wsgiref/handlers.py", line 280, in client_is_modern return self.environ['SERVER_PROTOCOL'].upper() != 'HTTP/0.9' TypeError: 'NoneType' object has no attribute '__getitem__' ----------------------------------------
This was at first quite perplexing because I checked response.py and self._closable_objects
gets assigned in HttpResponseBase.__init__
which gets called by HttpResponse.__init__
so it seemed impossible for an HttpResponse instance not to have the attribute. Unpickling doesn't normally call __init__
but this is not at all obvious or easy to track.
There are similar issues but with a bit more clear stacktrace if downgrading from 1.5 to 1.4:
Traceback: File "/home/catalin/hacking/envs/cached-httpresponse-across-versions/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response 89. response = middleware_method(request) File "/home/catalin/hacking/envs/cached-httpresponse-across-versions/lib/python2.7/site-packages/django/middleware/cache.py" in process_request 147. response = self.cache.get(cache_key, None) File "/home/catalin/hacking/envs/cached-httpresponse-across-versions/lib/python2.7/site-packages/django/core/cache/backends/filebased.py" in get 41. return pickle.load(f) Exception Type: ImportError at / Exception Value: No module named response
I'm surprised nobody seems to have run into this so far, googling for AttributeError on _closable_objects and searching the other bugs didn't show anything. I would imagine it's reasonably common to upgrade without restarting memcached or to have a filesystem based cache on a development machine where you don't bother to install memcached. And this happens on every view with @cache_page.
I don't know what a good solution would be. I can imagine Django doesn't want to guarantee pickle/unpickle round trips across versions as those can be hard to maintain but I think there should at least be a warning in the upgrade notes (of every version): when upgrading Django you need to delete your cache.
Attachments (2)
Change History (9)
comment:1 by , 11 years ago
Cc: | added |
---|
comment:2 by , 11 years ago
Component: | Core (Cache system) → Documentation |
---|---|
Triage Stage: | Unreviewed → Accepted |
by , 11 years ago
Attachment: | 20493.diff added |
---|
comment:3 by , 11 years ago
Has patch: | set |
---|---|
Summary: | HttpResponse objects cached with cache_page don't unpickle across Django version leading to strange errors → Add a warning that objects may not be picklable across Django versions |
Does the attached patch make sense? Would you say anything else?
comment:4 by , 11 years ago
I would make the suggestion a bit stronger, something like:
... you should consider clearing your cache after upgrading. Otherwise you may run into problems ...
I don't know if people clearly make the connection between "I'm using @cache_page" and "I'm caching pickled HttpResponse objects", so I see the stronger suggestion as a hint that you shouldn't just dismiss the next section (doesn't apply to me, I'm not pickling anything, I'm just using @cache_page).
But in general it sounds fine to me!
by , 11 years ago
Attachment: | 20493.2.diff added |
---|
comment:5 by , 11 years ago
Resolution: | → fixed |
---|---|
Status: | new → closed |
I think you're right that a requirement of cross-version unpicklability is more than we want to commit to. We do have an "upgrading Django" page in the docs now (https://docs.djangoproject.com/en/1.5/howto/upgrade-version/); seems like it would make a lot of sense to add this as a note there.
On a more long-term note, it might be worth considering whether the caching middleware should cache a very simple "body, headers" data structure instead of a full pickled
HttpResponse
. This would probably break all kinds of code, though, since it means anything else tacked on to theHttpResponse
(and perhaps used by middlewares further down the chain) would be missing.