diff options
author | Allan Crooks <allan@amcone.net> | 2014-05-04 14:39:10 +0100 |
---|---|---|
committer | Allan Crooks <allan@amcone.net> | 2014-05-04 14:39:10 +0100 |
commit | a400b3aa0ecd1dfcd909038b4c4e18a7441784df (patch) | |
tree | ef9958887ca1b1948bc2b3dafd2df22e220b4192 | |
parent | 975a1d68020320938b1dcc0efec529b69b9a2733 (diff) | |
download | cherrypy-git-a400b3aa0ecd1dfcd909038b4c4e18a7441784df.tar.gz |
Explicitly close response iterators when finished with them; this should help us clean up memory sooner when the iterators aren't fully consumed. Fixes #1314.
-rw-r--r-- | cherrypy/_cpwsgi.py | 4 | ||||
-rw-r--r-- | cherrypy/lib/__init__.py | 23 | ||||
-rw-r--r-- | cherrypy/lib/encoding.py | 15 |
3 files changed, 36 insertions, 6 deletions
diff --git a/cherrypy/_cpwsgi.py b/cherrypy/_cpwsgi.py index 4d1fd704..aaec99f7 100644 --- a/cherrypy/_cpwsgi.py +++ b/cherrypy/_cpwsgi.py @@ -13,7 +13,7 @@ import cherrypy as _cherrypy from cherrypy._cpcompat import BytesIO, bytestr, ntob, ntou, py3k, unicodestr from cherrypy import _cperror from cherrypy.lib import httputil - +from cherrypy.lib import is_closable_iterator def downgrade_wsgi_ux_to_1x(environ): """Return a new environ dict for WSGI 1.x from the given WSGI u.x environ. @@ -279,6 +279,8 @@ class AppResponse(object): def close(self): """Close and de-reference the current request and response. (Core)""" self.cpapp.release_serving() + if is_closable_iterator(self.iter_response): + self.iter_response.close() def run(self): """Create a Request object using environ.""" diff --git a/cherrypy/lib/__init__.py b/cherrypy/lib/__init__.py index 2c851ec4..a75a53da 100644 --- a/cherrypy/lib/__init__.py +++ b/cherrypy/lib/__init__.py @@ -16,6 +16,29 @@ def is_iterator(obj): # Types which implement the protocol must return themselves when # invoking 'iter' upon them. return iter(obj) is obj + +def is_closable_iterator(obj): + + # Not an iterator. + if not is_iterator(obj): + return False + + # A generator - the easiest thing to deal with. + import inspect + if inspect.isgenerator(obj): + return True + + # A custom iterator. Look for a close method... + if not (hasattr(obj, 'close') and callable(obj.close)): + return False + + # ... which doesn't require any arguments. + try: + inspect.getcallargs(obj.close) + except TypeError: + return False + else: + return True class file_generator(object): diff --git a/cherrypy/lib/encoding.py b/cherrypy/lib/encoding.py index 6d54ac8c..fa8387b6 100644 --- a/cherrypy/lib/encoding.py +++ b/cherrypy/lib/encoding.py @@ -4,6 +4,7 @@ import time import cherrypy from cherrypy._cpcompat import basestring, BytesIO, ntob, set, unicodestr from cherrypy.lib import file_generator +from cherrypy.lib import is_closable_iterator from cherrypy.lib import set_vary_header @@ -32,23 +33,27 @@ def decode(encoding=None, default_encoding='utf-8'): if not isinstance(default_encoding, list): default_encoding = [default_encoding] body.attempt_charsets = body.attempt_charsets + default_encoding - + class UTF8StreamEncoder: def __init__(self, iterator): self._iterator = iterator - + def __iter__(self): return self - + def next(self): return self.__next__() - + def __next__(self): res = next(self._iterator) if isinstance(res, unicodestr): res = res.encode('utf-8') return res - + + def close(self): + if is_closable_iterator(self._iterator) + self._iterator.close() + def __getattr__(self, attr): if attr.startswith('__'): raise AttributeError(self, attr) |