Break reference cycle created by default in Flask instances.
Flask instances with static folders were creating a reference cycle via their "static" view function (which held a strong reference back to the Flask instance to call its `send_static_file` method). This prevented CPython from freeing the memory for a Flask instance when all external references to it were released. Now use a weakref for the back reference to avoid this. Co-authored-by: Joshua Bronson <jab@users.noreply.github.com>
This commit is contained in:
parent
4e8b020494
commit
8efea0ccbb
2 changed files with 29 additions and 1 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import weakref
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
|
@ -478,11 +479,14 @@ class Flask(Scaffold):
|
||||||
assert (
|
assert (
|
||||||
bool(static_host) == host_matching
|
bool(static_host) == host_matching
|
||||||
), "Invalid static_host/host_matching combination"
|
), "Invalid static_host/host_matching combination"
|
||||||
|
# Use a weakref to avoid creating a reference cycle between the app
|
||||||
|
# and the view function (see #3761).
|
||||||
|
self_ref = weakref.ref(self)
|
||||||
self.add_url_rule(
|
self.add_url_rule(
|
||||||
f"{self.static_url_path}/<path:filename>",
|
f"{self.static_url_path}/<path:filename>",
|
||||||
endpoint="static",
|
endpoint="static",
|
||||||
host=static_host,
|
host=static_host,
|
||||||
view_func=self.send_static_file,
|
view_func=lambda **kw: self_ref().send_static_file(**kw),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set the name of the Click group in case someone wants to add
|
# Set the name of the Click group in case someone wants to add
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
|
import gc
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
|
import weakref
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from platform import python_implementation
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -16,6 +19,11 @@ from werkzeug.routing import BuildError
|
||||||
import flask
|
import flask
|
||||||
|
|
||||||
|
|
||||||
|
require_cpython_gc = pytest.mark.skipif(
|
||||||
|
python_implementation() != "CPython", reason="Requires CPython GC behavior",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_options_work(app, client):
|
def test_options_work(app, client):
|
||||||
@app.route("/", methods=["GET", "POST"])
|
@app.route("/", methods=["GET", "POST"])
|
||||||
def index():
|
def index():
|
||||||
|
|
@ -1970,3 +1978,19 @@ def test_max_cookie_size(app, client, recwarn):
|
||||||
|
|
||||||
client.get("/")
|
client.get("/")
|
||||||
assert len(recwarn) == 0
|
assert len(recwarn) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@require_cpython_gc
|
||||||
|
def test_app_freed_on_zero_refcount():
|
||||||
|
# A Flask instance should not create a reference cycle that prevents CPython
|
||||||
|
# from freeing it when all external references to it are released (see #3761).
|
||||||
|
gc.disable()
|
||||||
|
try:
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
assert app.view_functions["static"]
|
||||||
|
weak = weakref.ref(app)
|
||||||
|
assert weak() is not None
|
||||||
|
del app
|
||||||
|
assert weak() is None
|
||||||
|
finally:
|
||||||
|
gc.enable()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue