Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
After BigPipe is enabled, the scripts added twice into page that will cause the script executes twice.
reproduce:
- @see #5
- Enables BigPipe and locate to any content node/*
Proposed resolution
Excluded loaded scripts on further page loading.
Remaining tasks
- (Needs to idenify if it affected CSS)
- Patch
User interface changes
-
API changes
-
Data model changes
-
Comment | File | Size | Author |
---|---|---|---|
#29 | 2903614-29.patch | 2.74 KB | viappidu |
#25 | bp.png | 160.1 KB | droplet |
#19 | core-ajaxload-unique-2903614-19.patch | 3.01 KB | nod_ |
#9 | find-bug.patch | 1.12 KB | droplet |
#8 | c20170824_175840.png | 499.5 KB | droplet |
Issue fork drupal-2903614
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
Wim LeersI can't reproduce this.
EDIT: to be clear: great find! Thank you! But of course, we need to be able to reproduce the bug to understand the root cause, and to be able to fix it :)
Comment #3
Wim LeersWe should be able to create a failing test.
All regression tests for BigPipe live at
\Drupal\Tests\big_pipe\FunctionalJavascript\BigPipeRegressionTest
.Comment #4
Wim LeersAlso, this does not meet the criteria for a major bug: https://www.drupal.org/core/issue-priority#major-bug — but it does meet the definition of a normal bug: https://www.drupal.org/core/issue-priority#normal-bug.
Comment #5
droplet CreditAttribution: droplet commentedHmm.. So frustrated... I can't reproduce it 100% now.... But.. I can reproduce with simplytest.me without patch.
23 hrs left: https://d2ikq.ply.st
Here's what I do (recall from my memory):
1. https://simplytest.me/project/drupal/8.5.x
2. Install
3. Enable big pipe
4. Create a new node/1
5. reload page
6. add new comment
7. reload page
8. hit the bug.
Note 1: Clear browser caches no help
Note 2: Clear Drupal caches no help
Note 3: Clear Cookies helps but after login reload the page few time, the bug comes back
Note 4: I created node/2 and can't reproduce the bug :S
Note 5: The first time I found the bug in my local env, which is disabled "Aggregate JavaScript files" all time. (using settings.local.php)
Note 6: I've tested on private mode without any plugins
I don't know how bigPipe works. Could the script executions time affect it? the Ajax call posting `ajax_page_state[libraries]` data
Attached network .har
Hope it helps :)
Comment #6
Wim LeersI still can't reproduce it, even if I follow your steps exactly. Sorry 😥
BigPipe determines which scripts need to be loaded on the server side. So script execution times do not affect it. I appreciate you adding #1988968: Drupal.ajax does not guarantee that "add new JS file to page" commands have finished before calling said JS, which is indeed a conceptually related issue, but it's technically impossible for that issue to cause this. Impressive association though :)
See
\Drupal\big_pipe\Render\BigPipe::sendPlaceholders()
for how sending BigPipe placeholders works. The class docblock at the top of\Drupal\big_pipe\Render\BigPipe
explains the entire process.Comment #7
droplet CreditAttribution: droplet commentedBut the 2nd loads come from
/quickedit/attachments?_wrapper_format=drupal_ajax
. Look at the HAR, ajax_page_state[libraries] didn't include `ckeditor*`.** HAR captured from a non-patch env. :s
Is it possible the Quickedit Ajax request sent before BigPipe Ajax (bigPipeProcessPlaceholderReplacement)?
If the assumption is true, we need to check the drupalSetting.ajaxPageState before script insertion. (Or change big_pipe.js weights)
Let me see if I can delay the script loading with proxy tools.
Comment #8
droplet CreditAttribution: droplet commentedCool I can reproduce it with my macbook also. Both Chrome & (fresh installed) Firefox also this time.
Keeping press `Command + Shift + R` to reload the page. And try to clean drupal caches and create a new node and keeping reload until catch it.
and my assumption is correct. the ajax `attachments` called before others.
Non BigPipe:
Page Rendering (have correct data in drupalSetting) -> `attachments` ajax
BigPipe:
non-buggy path:
Page Rendering (part of drupalSetting) -> Keep to render and merge setting (async) -> `attachments` ajax (async)
buggy path:
Page Rendering (part of drupalSetting) -> `attachments` ajax (async) -> Keep to render and merge setting (async)
By theory, it makes sense now.
Comment #9
droplet CreditAttribution: droplet commentedCool. Apply the patch and find an auto-reload browser extension and set `Preserve log` in your console.
For some reason, it's easier to reproduce the bug with Chrome Canary Build. (But my Firefox is a stable version)
Add `sleep(2)` to a particular point that delay sending data to frontend in BigPipe PHP side would easier to reproduce it I thought. BigPipe guys turn.
Comment #10
Wim Leers@droplet+++++++++++++++
Investigating :)
Comment #11
Wim LeersI now understand the problem, and why it's hard to reproduce (and why using a particular browser may make it more likely to be reproducible): this is a race condition.
Analysis
Explained very simply: this is a race condition between BigPipe and Quick Edit.
Explained in more detail: this is a race condition on the client-side, but affected by network latency and server response times. Specifically:
big_pipe.js
is checking every 50 milliseconds whether new content has arrived.@InPlaceEditor
plugins). For this, metadata needs to be available, which is first attempted to be found inlocalStorage
, but if the metadata doesn't exist yet there, then a request is made to the server. Finally, all the CSS+JS for those in-place editor plugins is loaded, which requires a request. That's up to 2 requests, but at minimum 1 request (the latter), if the necessary metadata is cached on the client side.The race condition then is:
editor.js
+ckeditor.js
will already have been loaded by BigPipe. Which means that when Quick Edit runs, it will no longer load those files.editor.js
+ckeditor.js
. The BigPipe server side doesn't ever know which additional asset libraries have been loaded on the client side though… so it never will respect JS loaded on the client side, because BigPipe on the server side isn't ever informed of changes on the client side (which also doesn't make sense, because 99% of the time BigPipe on the server side is already rendering the content to flush by the time that JS is executing).Root cause
The root cause of this problem is not BigPipe though. The root cause is this in
\Drupal\Core\Ajax\AjaxResponseAttachmentsProcessor::buildAttachmentsCommands()
:Note that we have a specific "add CSS" command, but no specific "add JS" commands. For JS, we're simply inserting HTML. So for CSS, we have a semantically accurate command, which we can make "smarter". For JS, we do not.
If we'd have "add CSS" and "add JS" commands, then we'd be able to make
Drupal.AjaxCommands.prototype.add_css
andDrupal.AjaxCommands.prototype.add_js
take into account CSS and JS that was just loaded already, due to a race condition between two independent pieces of logic.Next steps
This technically belongs to the
ajax system
component. Would you agree?Given the root cause, this is actually more closely related to #1988968: Drupal.ajax does not guarantee that "add new JS file to page" commands have finished before calling said JS than I would've anticipated earlier! That wouldn't resolve the race condition in this particular case (because one piece of logic is wholly client-side and the other is wholly server-side), but it'd introduce the basic infrastructure we need to solve this bug. And it would solve the race condition if the two pieces of logic determining which additional assets to load were solely JS.
Once #1988968: Drupal.ajax does not guarantee that "add new JS file to page" commands have finished before calling said JS is solved, we'd be able to further enhance the
add_js
command, and make it smart enough to not again load the same JS.Comment #13
Wim LeersPer #11.
Comment #17
nicxvan CreditAttribution: nicxvan commentedAfter applying the fix in https://www.drupal.org/project/drupal/issues/1988968 to allow us to attach a library as ajax, other scripts began firing twice.
We are not using Quick edit, but also disabling BigPipe fixes the race condition.
Comment #18
nod_We have the .once function to prevent issues with multiple triggers, does you code use that and you're still seeing a problem?
Comment #19
nod_to apply on top of #1988968-185: Drupal.ajax does not guarantee that "add new JS file to page" commands have finished before calling said JS.
Patch isn't following coding standards to minimize the diff.
@nicxvan: with the two patch is it better?
Comment #20
droplet CreditAttribution: droplet commented@nod_,
That should work if `ajax.js` is the single point to add JS.
BUT you can't deduplicate like above here. If you wanted to do that in JS side, it should pass a `?querystring=skip` for extra handler
PHP -> ADD JS -> HTML
PHP -> ADD JS -> (NOT ADDED) (<~~ BUG)
You made an assumption on particular code logic. e.g. A reusable function and 2-nd call to that function.
BUT, you can attach the same file with JavaScript statement twice on purpose. (this bug is a good example)
Comment #21
nod_I couldn't reproduce the issue so hard to test on my end.
If there are uncertainties we can restrict possibilities, for exemple you have to load scripts through loadjs, using the ajax command or not. I started with
.filter(script => !document.querySelector(`script[src="${script.src}"]`))
before going with loadjs features. But that works just as well (or doesn't work just as well :p)I'm not sure what are the other ways to add script to the page that would cause an issue? Bigpipe uses the ajax object. just like we can only attach whole libraries and not single JS or CSS files anymore we can restrict things so that it'll work as expected. Also I question the need to load a js file twice on purpose, what's the use case?
Comment #22
nod_Comment #23
nicxvan CreditAttribution: nicxvan commented@nod_ sorry for the delay and thanks for the quick reply. .once with the patch took care of the issue, everything is working as expected.
Comment #24
droplet CreditAttribution: droplet commented@nod_,
We must admit some facts or my thoughts here:
PHP -> ADD JS -> HTML (<~~ GREEN)
PHP -> ADD JS -> (NOT ADDED) (<~~ BUG)
PHP -> ADD JS (MARKED_CAN_SKIP) (<~~ Providing a way to deal the limiation)
PHP -> ADD JS (PATCHED_TO_ALWAYS_SKIP) (<~~ New standard, nothing worth to discuss)
If I design it for Drupal only,
Assumed the BigPipe is rendering sequentially (and clientside only). We should only compare to `step - 1` to skip the adding.
There's 2 way:
SIMPLE: compare the script_path only. (buggy I think)
BETTER: return back the state from backend, and compare diff between prev_state & current_state
Even `BETTER` method, I think there's some situation we return the response conditionally based on the prev_state. Therefore, it must also return prev_state with extra metadata.
maybe a remote collaboration tool and I want to pull the data each 50ms. Or I have a script to execute it when I click the button.
console.log('I only want to make changes this way even you told me this is bad coding')
I think we able to control the rendering by hook the debuggers into both PHP & JS to pause the executions
EDIT:
this bug revealed BigPipe limited the conditional process (maybe something more than return JS files or not) on the backend (before and after a simple fix), right? If not, it's likely a bug on PHP side.
Comment #25
droplet CreditAttribution: droplet commentedthe simplest way to kick out this bug (, or as temp solution?)
execute
ajaxresponse processer (bigPipeProcessDocument) directly.Sorry, I don't have enough time to understand BigPipe (PHP) thoroughly. I dropped few
sleep(5);
around the code. `vnd.drupal-ajax` still rendered before other parts. Is it guaranteed "asset libraries" send before other parts?it seems TRUE :
https://git.drupalcode.org/project/drupal/-/blob/9.0.x/core/modules/big_...
Comment #26
droplet CreditAttribution: droplet commenteda few reports in the #1988968: Drupal.ajax does not guarantee that "add new JS file to page" commands have finished before calling said JS and may be related.
I'd like to jot a note.
After a few years, this is 100% reproducible in my local env. I believed if you have a FAST CPU & FAST WEBSERVER CONFIG. Something like to render everything under 500ms (see my img above, that's around 4xx ms from Performance API), you will 100% hit this bug.
For remote webserver the probability is lower than 1% I think. (I haven't performed any real tests to find it out)
For local: 100% for fast hardware.
Also, the workaround in #25 has no harms I believe.
EDITED:
Also, ajax patch isn't a solution for this bug. Even with @nod_ patch above.
Comment #28
viappidu CreditAttribution: viappidu as a volunteer commentedRewrite for current 9.1.x, now depending on #1988968-213: Drupal.ajax does not guarantee that "add new JS file to page" commands have finished before calling said JS
Comment #29
viappidu CreditAttribution: viappidu as a volunteer commentedCorrected to apply on latest patch on #1988968-216: Drupal.ajax does not guarantee that "add new JS file to page" commands have finished before calling said JS
Comment #33
mstrelan CreditAttribution: mstrelan at PreviousNext commentedBumping to PP-2 since #1988968: Drupal.ajax does not guarantee that "add new JS file to page" commands have finished before calling said JS is PP-1.
Comment #35
nod_Tha add_js command made it in
Comment #36
Wim LeersNow that Quick Edit no longer lives in Drupal core … is this really still relevant as a core bug? 🤔
Comment #38
Wim LeersIs this still a problem? 🤔