Resolve debounce race condition - #2356
Conversation
| const slowjax = debounce(ajax, 200); | ||
|
|
||
| function handleAjaxResponse(requestId) { | ||
| const handleAjaxResponse = debounce(async (requestId) => { |
There was a problem hiding this comment.
Instead of throttling the request, we debounce the entire function and properly cancel it if it is replaced by a new call.
debounce didn't cancel the execution but only deplayed the resolution. It then resolved multiple times with the same values casing the resulting code to be called multiple times in direct succession on multiple threads. This can not only resolve into multipe DOM updates but also to race conditions.
There was a problem hiding this comment.
Pull request overview
This PR fixes a race condition caused by the existing debounce implementation resolving multiple pending calls, which could trigger repeated UI updates for rapid history-panel fetches.
Changes:
- Reworked
debounceto cancel prior scheduled invocations instead of resolving multiple pending calls. - Updated toolbar AJAX update flow to debounce the handler rather than debouncing
ajax()calls with multiple.then(...)continuations. - Adjusted JS unit tests and added a changelog entry.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
debug_toolbar/static/debug_toolbar/js/utils.js |
Reimplements and documents debounce, updates module exports. |
debug_toolbar/static/debug_toolbar/js/toolbar.js |
Refactors AJAX update logic to use the revised debounce behavior and avoid repeated DOM updates. |
tests/js/utils.test.js |
Updates debounce tests for the new behavior (sync + async cases). |
docs/changes.rst |
Adds a Pending changelog entry for the debounce race condition fix. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
I don't know why the linter hates me. I didn't even touch the Python code :/ |
matthiask
left a comment
There was a problem hiding this comment.
Promise.try is very new; it is only available in Firefox since the beginning of 2025. I think that's fine since people can test their sites in Browserstack or whatever without the toolbar enabled if they encounter errors, but it's something to keep in mind.
@django-commons/django-debug-toolbar-committers Does anyone have any reservations regarding browser compatibility?
Maybe |
|
Ok, done. Ready form my side. Unless there are any concerns. I amended the changes in new comments; feel free to squash merge, but I can also rebase 🤷♂️ |
Firefox esr is at version 140 so It should be find with organisation. That's seem a good time to add a section prequesite with minimal browser configuration. Promise should be the newer fonction with us so take the minimal for it. |
|
Thanks @elineda for the review! I'm working on an addition to the docs now. Thanks @codingjoe! I almost always use squash merges here except on larger changes where preserving the steps makes the history more understandable later. Cleaning up the history manually isn't worth anyones time elsewise. |
… modern browser (#2357) Co-authored-by: Johannes Maron <johannes@maron.family>
Description
Debounce didn't cancel the execution but only delayed the resolution. It then resolved multiple times with the same values, causing the resulting code to be called multiple times in direct succession on multiple threads.
This can not only result in multiple DOM updates but also in race conditions.
Note
This uses
Promise.try, Baseline 2025 "newly available". Since we are in a debug and developer context, I much prefer the beautify simplify it provides, even if Opera Mini doesn't support it.That no one found this bug in years underlines my point ;)
Checklist:
docs/changes.rst.AI/LLM Usage
[ ] This PR includes code generated with the help of an AI/LLM