Skip to content

Commit

Permalink
re work on staticfiles panel with contextvars and signals
Browse files Browse the repository at this point in the history
  • Loading branch information
salty-ivy committed Aug 5, 2024
1 parent d076a7e commit 2edc684
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 22 deletions.
12 changes: 5 additions & 7 deletions debug_toolbar/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ async def __acall__(self, request):
print(f"thread_id in __acall__ main thread: {threading.get_ident()}")
show_toolbar = get_show_toolbar()
if not show_toolbar(request) or DebugToolbar.is_toolbar_request(request):
print("***************** toolbar request")
response = await self.get_response(request)
return response

Expand All @@ -128,9 +127,9 @@ async def __acall__(self, request):
panel.enable_instrumentation()
try:
# Run panels like Django middleware.
print(f"Context before awaiting process_request: {id(copy_context())}")
# print(f"Context before awaiting process_request: {id(copy_context())}")
response = await toolbar.process_request(request)
print(f"Context after awaiting process_request: {id(copy_context())}")
# print(f"Context after awaiting process_request: {id(copy_context())}")
finally:
clear_stack_trace_caches()
# Deactivate instrumentation ie. monkey-unpatch. This must run
Expand All @@ -152,7 +151,6 @@ def _postprocess(self, request, response, toolbar):

# Always render the toolbar for the history panel, even if it is not
# included in the response.
print("rendering toolbar")
rendered = toolbar.render_toolbar()

for header, value in self.get_headers(request, toolbar.enabled_panels).items():
Expand All @@ -173,9 +171,9 @@ def _postprocess(self, request, response, toolbar):
if "Content-Length" in response:
response["Content-Length"] = len(response.content)

print("enabling it instrumentation of staticfilepanel back again")
static_file_panel = toolbar.get_panel_by_id("StaticFilesPanel")
static_file_panel.enable_instrumentation()
# print("enabling it instrumentation of staticfilepanel back again")
# static_file_panel = toolbar.get_panel_by_id("StaticFilesPanel")
# static_file_panel.enable_instrumentation()
return response

@staticmethod
Expand Down
63 changes: 48 additions & 15 deletions debug_toolbar/panels/staticfiles.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from multiprocessing import Manager
import contextlib
import uuid
from contextvars import ContextVar, copy_context
from os.path import join, normpath
from threading import get_ident

from django.conf import settings
from django.contrib.staticfiles import finders, storage
Expand Down Expand Up @@ -31,9 +34,8 @@ def url(self):


# This will collect the StaticFile instances across threads.
# used_static_files = ContextVar("djdt_static_used_static_files")
manager = Manager()
shared_store = manager.dict() # We can also use a list, just like the contextvar.
used_static_files = ContextVar("djdt_static_used_static_files")
request_id_store = ContextVar("request_id for unique identification of requests")


class DebugConfiguredStorage(LazyObject):
Expand All @@ -59,8 +61,19 @@ def _setup(self):
class DebugStaticFilesStorage(configured_storage_cls):
def url(self, path):
# used_static_files.get().append(StaticFile(path))]
shared_store[path] = StaticFile(path)
staticfiles_used_signal.send(sender=self, staticfiles=StaticFile(path))
print(f"Context before sending signal: {id(copy_context())}")
print(f"thread_id in sync_to_async thread: {get_ident()}")

print(StaticFile(path))

with contextlib.suppress(LookupError):
request_id = request_id_store.get()
staticfiles_used_signal.send(
sender=self,
staticfiles=StaticFile(path),
request_id=request_id,
context=id(copy_context()),
)
return super().url(path)

self._wrapped = DebugStaticFilesStorage()
Expand Down Expand Up @@ -89,21 +102,41 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.num_found = 0
self.used_paths = []
self.request_id = str(uuid.uuid4())

def enable_instrumentation(self):
# as the instrumentation keeps enabled, so requests for debug_toolbar can also store file paths in store
# clear the shared store to remove any static files stored from previous requests.
shared_store.clear()

# shared_store.clear()
storage.staticfiles_storage = DebugConfiguredStorage()
staticfiles_used_signal.connect(self._store_staticfile_info)

def _store_staticfile_info(self, sender, staticfiles, **kwargs):
print("signal called", staticfiles)
used_static_files.set([])
request_id_store.set(self.request_id)

def _store_staticfile_info(
self, sender, staticfiles, request_id, context, **kwargs
):
# print("signal called")
print(f"Context in signal: {context}")
# print(f"thread_id in sync_to_async thread: {get_ident()}")

with contextlib.suppress(LookupError):
# For LookupError:
# The ContextVar wasn't set yet. Since the toolbar wasn't properly
# configured to handle this request, we don't need to capture
# the static file.
if request_id_store.get() == self.request_id:
staticfiles_list = used_static_files.get()
staticfiles_list.append(staticfiles)

def disable_instrumentation(self):
storage.staticfiles_storage = _original_storage
print("diable instrumentation")
# storage.staticfiles_storage = _original_storage
for receiver in staticfiles_used_signal.receivers:
print(receiver)
staticfiles_used_signal.disconnect(self._store_staticfile_info)
with contextlib.suppress(LookupError):
print(used_static_files.get())

@property
def num_used(self):
Expand All @@ -128,9 +161,9 @@ def process_request(self, request):
return response

def generate_stats(self, request, response):
# print("generate_stats for staticfiles panel")
file_paths = shared_store.values()
shared_store.clear() # clear the shared store
print("context in storing stats: ", id(copy_context()))
file_paths = used_static_files.get().copy()
used_static_files.get().clear()
self.record_stats(
{
"num_found": self.num_found,
Expand Down

0 comments on commit 2edc684

Please sign in to comment.