Skip to content

Commit 4726e0f

Browse files
committed
incoperate signals and contextvars to record staticfiles for concurrent requests
1 parent aea6cc6 commit 4726e0f

File tree

1 file changed

+38
-14
lines changed

1 file changed

+38
-14
lines changed

debug_toolbar/panels/staticfiles.py

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import contextlib
2+
import uuid
23
from contextvars import ContextVar
34
from os.path import join, normpath
45

56
from django.conf import settings
67
from django.contrib.staticfiles import finders, storage
8+
from django.dispatch import Signal
79
from django.utils.functional import LazyObject
810
from django.utils.translation import gettext_lazy as _, ngettext
911

@@ -29,7 +31,9 @@ def url(self):
2931

3032

3133
# This will collect the StaticFile instances across threads.
32-
used_static_files = ContextVar("djdt_static_used_static_files")
34+
used_static_files = ContextVar("djdt_static_used_static_files", default=[])
35+
request_id_context_var = ContextVar("djdt_request_id_store")
36+
record_static_file_signal = Signal()
3337

3438

3539
class DebugConfiguredStorage(LazyObject):
@@ -59,7 +63,12 @@ def url(self, path):
5963
# The ContextVar wasn't set yet. Since the toolbar wasn't properly
6064
# configured to handle this request, we don't need to capture
6165
# the static file.
62-
used_static_files.get().append(StaticFile(path))
66+
request_id = request_id_context_var.get()
67+
record_static_file_signal.send(
68+
sender=self,
69+
staticfile=StaticFile(path),
70+
request_id=request_id,
71+
)
6372
return super().url(path)
6473

6574
self._wrapped = DebugStaticFilesStorage()
@@ -73,7 +82,7 @@ class StaticFilesPanel(panels.Panel):
7382
A panel to display the found staticfiles.
7483
"""
7584

76-
is_async = False
85+
is_async = True
7786
name = "Static files"
7887
template = "debug_toolbar/panels/staticfiles.html"
7988

@@ -88,12 +97,25 @@ def __init__(self, *args, **kwargs):
8897
super().__init__(*args, **kwargs)
8998
self.num_found = 0
9099
self.used_paths = []
100+
self.request_id = str(uuid.uuid4())
101+
102+
def _store_static_files_signal_handler(self, sender, staticfile, **kwargs):
103+
with contextlib.suppress(LookupError):
104+
# Only record the static file if the request_id matches the one
105+
# that was used to create the panel.
106+
# as sender of the signal and this handler will have multiple
107+
# concurrent connections and we want to avoid storing of same
108+
# staticfile from other connections as well.
109+
if request_id_context_var.get() == self.request_id:
110+
used_static_files.get().append(staticfile)
91111

92112
def enable_instrumentation(self):
93113
storage.staticfiles_storage = DebugConfiguredStorage()
114+
record_static_file_signal.connect(self._store_static_files_signal_handler)
115+
request_id_context_var.set(self.request_id)
94116

95117
def disable_instrumentation(self):
96-
storage.staticfiles_storage = _original_storage
118+
record_static_file_signal.disconnect(self._store_static_files_signal_handler)
97119

98120
@property
99121
def num_used(self):
@@ -109,18 +131,20 @@ def nav_subtitle(self):
109131
"%(num_used)s file used", "%(num_used)s files used", num_used
110132
) % {"num_used": num_used}
111133

112-
def process_request(self, request):
113-
reset_token = used_static_files.set([])
114-
response = super().process_request(request)
115-
# Make a copy of the used paths so that when the
116-
# ContextVar is reset, our panel still has the data.
117-
self.used_paths = used_static_files.get().copy()
118-
# Reset the ContextVar to be empty again, removing the reference
119-
# to the list of used files.
120-
used_static_files.reset(reset_token)
121-
return response
134+
# def process_request(self, request):
135+
# reset_token = used_static_files.set([])
136+
# response = super().process_request(request)
137+
# # Make a copy of the used paths so that when the
138+
# # ContextVar is reset, our panel still has the data.
139+
# self.used_paths = used_static_files.get().copy()
140+
# # Reset the ContextVar to be empty again, removing the reference
141+
# # to the list of used files.
142+
# used_static_files.reset(reset_token)
143+
# return response
122144

123145
def generate_stats(self, request, response):
146+
self.used_paths = used_static_files.get().copy()
147+
used_static_files.get().clear()
124148
self.record_stats(
125149
{
126150
"num_found": self.num_found,

0 commit comments

Comments
 (0)