summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAllan Crooks <allan@amcone.net>2014-05-04 14:39:10 +0100
committerAllan Crooks <allan@amcone.net>2014-05-04 14:39:10 +0100
commita400b3aa0ecd1dfcd909038b4c4e18a7441784df (patch)
treeef9958887ca1b1948bc2b3dafd2df22e220b4192
parent975a1d68020320938b1dcc0efec529b69b9a2733 (diff)
downloadcherrypy-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.py4
-rw-r--r--cherrypy/lib/__init__.py23
-rw-r--r--cherrypy/lib/encoding.py15
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)