Skip to content

ClosingIterator does not close wrapped iterable #1259

@romank0

Description

@romank0

ClosingIterator does not respect wsgi specification namely this piece:

If the iterable returned by the application has a close() method, the server or gateway must call that method upon completion of the current request

The problem is that ClosingIterator tries to invoke close on the iterator returned by the iterable but it should invoke close on the iterable itself.

Here's relevant part of the example server implementation from the wsgi specification:

    result = application(environ, start_response)
    try:
        for data in result:
            if data:    # don't send headers until body appears
                write(data)
        if not headers_sent:
            write('')   # send headers now if body was empty
    finally:
        if hasattr(result, 'close'):
            result.close()

Note that close is invoked on the object returned by the application.

This causes the problem as close is not invoked at all. I face this problem in a django application that runs in gunicorn which is setup to use gevent async workers. In order to isolate greenlets werkzeug LocalManager is used to wrap django wsgi application. As close is not invoked django does not do necessary cleanup and does not close database connections. The problem that is described in django documentation happens:

screen shot 2018-03-01 at 14 44 09

Here's the smallest example that demonstrates the problem.
I used AppClass from the wsgi specification with a slight modification that adds a close method to it.

  1. install two requirements into virtual env:
uwsgi==2.0.17
Werkzeug==0.14.1
  1. create app.py file with content below
  2. run server bin/uwsgi --http :9090 --wsgi-file app.py
  3. execute request curl "http://localhost:9090"

If you set wrap to False wsgi application is not wrapped and closed is printed to stdout. If the application is wrapped with ClosingIterator then closed is not invoked and nothing is printed.

app.py file:

class AppClass:
    def __init__(self, environ, start_response):
        self.start = start_response

    def __iter__(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield b"Hello World"

    def close(self):
        print "closed"

wrap = True

if wrap:
    def application(environ, start_response):
        from werkzeug.wsgi import ClosingIterator
        return ClosingIterator(AppClass(environ, start_response))
else:
    application = AppClass

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions