Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cornice @view decorator creates a reference cycle #583

Open
romuald opened this issue Mar 8, 2024 · 0 comments
Open

Cornice @view decorator creates a reference cycle #583

romuald opened this issue Mar 8, 2024 · 0 comments

Comments

@romuald
Copy link

romuald commented Mar 8, 2024

When debugging an issue with our application, we found out that cornice creates a reference cycle with the request object

In cornice/service.py:

request.cornice_args = (args, ob)

ob here is (can be?) an application object that was initialized with the request object

So request is referencing an application object which is referencing the request, which creates a reference cycle.

Those are not inherently bad but this makes it so the request will not be cleaned up at the end of its life cycle, and some related resources will also not be freed up until then

A practical example is a large JSON body sent to the webserver: webob will create a temporary file, that will only be deleted on the next garbage collection (that will raise a ResourceWarning, but only during tests)

Here is a minimal example exposing the problem, disabling the gc will lead to a ever-increasing number of requests

import gc
from wsgiref.simple_server import make_server

from cornice.resource import resource, view
from pyramid.config import Configurator
from pyramid.request import Request


@resource(collection_path="/submit", path="/submit/{id}")
class HelloWorldResource:
    def __init__(self, request, context=None):
        self.request = request
        self.context = context

    @view()
    def collection_post(self):
        # find number of in-memory pyramid requests
        count = sum(1 for x in gc.get_objects() if isinstance(x, Request))
        return {"message": f"I have {count} request(s) in memory."}


def main():
    with Configurator() as config:
        config.include("cornice")
        config.scan()
        app = config.make_wsgi_app()
        return app


if __name__ == "__main__":
    app = main()
    server = make_server("0.0.0.0", 6543, app)
    print("Server running on http://localhost:6543")

    gc.collect()

    # disable gc for demonstration
    gc.disable()
    server.serve_forever()

In our case we'll be fixing the issue by using

class XXXResource:
    def __init__(self, request, context=None):
        self.request = weakref.proxy(request)

The issue could be fixed by using params=dict(request=weakref.proxy(request)) in the original source, but it may have side effect I'm not foreseeing (this does not break the tests however)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant