Problem/Motivation
Large placeholders are not rendered. By large I mean let's say 1.5MB of data, but on our site, it was a chunk of 300kb with an exception.
Steps to reproduce
In #3196973 we introduced the ability to use MutationObserver API. The problem here is in the following:
1. The Observer is configured with only one option childList: true which means that it will only look for new children.
2. If the server returns a large chunk of data it is streamed and may not be fully loaded at the moment when the Mutation observer is triggered.
3. Afterwards it will simply try to parse JSON which will not be valid at the moment and never re-try to check it again.
const response = mapTextContentToAjaxResponse(content);
if (response === false) {
return;
}
So, here is how to reproduce it on barebones Drupal.
Place a custom block with long text that has more than 1.5MB ( I didn't test how small should be text, just put inside a large text ) and make sure that block will not be cacheable, either using block_alter hook or using for instance blocache mobule, so the block will be lazy builded
Here is a link to simplytest site where I used https://www.drupal.org/project/blocache module and disabled cache for a custom block that is placed in Block layout (admin/structure/block) so the block is always lazy_builded. The block is obviously always present for anonymous users, but if you log in (creds are I thought Simplitest will work more than one day.user: admin, pass: admin) it will be almost always not rendered.
Proposed resolution
So there are a lot of possible solutions. I'll attach two patches for the 1-st and 2-st solutions.
-
Is to track all nodes that have invalid JSON
if (response === false) { HERE WE PUT A NODE INTO AN ARRAY return; }And on
DOMContentLoadedprocess them all.
With such an approach we are sure that everything will be rendered but at the end of the page loading.
-
The second approach Is to add additional configuration options to MutationObserver and rework it a bit. Buy this I mean:
- To add
subtree: true, characterData: trueconfiguration optoins. It will ensure that the observer will be triggered each time text node inside subtree nodes is changed. This is actually what we need because this will track changes made to text nodes (by text node I mean the response of a placeholder that is partially present insidescriptplaceholder node.) - Rework a bit how we process mutations so it will also track changed made to
characterDatamutation type
In such a way, we will ensure that a placeholder will be rendered as soon as the browser fully loads it. I'm not sure about performance issues with such implementation.
- To add
- Another solution would be to revert changes made in #3196973. And drop MutationObserver usage for placeholder processing.
Remaining tasks
Decide which solution is suitable.
User interface changes
-
API changes
-
Data model changes
-
Release notes snippet
-
| Comment | File | Size | Author |
|---|
Issue fork drupal-3387039
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
dinazaur commentedPatch with #1 solution.
Comment #3
dinazaur commentedPatch with #2 solution
Comment #4
dinazaur commentedPatch with Functional test that does not work.
Comment #5
dinazaur commentedComment #6
abrammHi @dinazaur, you did a really nice job explaining the issue! :)
One more idea though - in approach 1 I don't like an idea of processing chunks out of order; probably we could also check if there are previos unprocessed chunks each time a _new_ chunk is loaded?
Comment #7
smustgrave commentedThank you for reporting.
Can we get a test only patch showing the issue.
Comment #8
dinazaur commentedPlease read the summary.
From what I understand it's impossible to implement a test for this.
Comment #9
smustgrave commentedCould tests not be added like BigPipeStrategyTest
Comment #10
dinazaur commentedI have attached the patch with the "working" test in #4, the problem with this test is it always passes, but it should not. That's why I also added a link to simplytest site where I simulated same situation, and everyone can check it and see that big pipe does not work.
Comment #11
dinazaur commentedComment #12
smustgrave commentedAh okay I’ll leave for another reviewer then to see what they think. Don’t want to mark anything without test coverage personally.
Comment #13
dinazaur commentedComment #15
dinazaur commentedThe PR contains solution #2 because it seems to be the appropriate way of fixing the issue.
Comment #16
dinazaur commentedComment #17
dinazaur commentedOkay, I actually managed to consistently reproduce the bug on my local machine, hopefully, it will fail now inside Drupal CI.
Comment #18
dinazaur commentedFixed cspell error.
Comment #20
dinazaur commentedYep, as you can see #18 test failed. In that patch I have attached barebones test without the fix. Instead MR with test and fix is green.
Comment #21
dinazaur commentedComment #22
smustgrave commentedTo save space what do you think of doing
In the template.
Also hiding the test-only patch so the review bot doesn't pick it up.
Comment #23
dinazaur commentedNice catch! Used loop instead of raw text. Just in case I'm attaching a patch with the updated version here, I want to be sure that it will fail.
Comment #24
dinazaur commentedComment #26
dinazaur commentedNice, it failed.
Comment #27
lysenko+1 RTBC
Comment #28
catchComment #29
ralphmoser commentedI can confirm the issue since Drupal 10.0. I'm using an XYBlockBase class inheriting BlockBase in my module, that lazy-loads the block body using the big-pipe technology. This class is extensively used in my module, mainly to load lists of rendered entities with content sizes from 10k to more than 1M. Since I've updated my Drupal instance to version 10 last week, all these blocks didn't load its content anymore due to the above described issue of an incomplete script-node, when the MutationObserver is triggered.
Side note: The upgrade to Drupal 10 worked on my local development environment for smaller content (<40k), but failed on the stage server. So the streamed size of the script-node content, when the MutationsObserver is triggered, depends on the runtime environment.
The patch #2 solves this issue for all lazy-loading blocks. The solution of patch #2 is clean and not invasive, because all non-JSON-parsable script node processing is just postponed until the document loading is complete (DOMContentLoaded event).
PS: The references to the non-parsable script nodes stay the same, because only the script-node content is updated by the HTTP streaming of large placeholders.
Comment #30
catchBumping this to critical - bits of the page just randomly go missing rendering the site potentially unusable, multiple reports in what look like duplicate issues.
Comment #31
nod_+1 on the approach, could use a couple of comments for future us.
Comment #32
catchNeeds work to address @nod_'s feedback.
Comment #33
dinazaur commentedAdded additional comments to the code, ready for review.
Comment #34
smustgrave commentedHiding patches to make it easier to show work is in the MR.
Appears the threads and feedback have been addressed.
Comment #35
catchI would really like to see some manual testing on this issue from the people reporting #3390178: big_pipe sometimes fails to load blocks (or with steps to reproduce from that issue), which seems to be more about lots of placeholders rather than large placeholders but feels like could be a different symptom of the same thing.
If it doesn't fix the issues on there, we might want to consider rolling back #3196973: Use Mutation observer for BigPipe replacements altogether than opening a new issue to re-introduce it along with this fix and anything else necessary.
On the other hand if it does fix people's issues on there, I'd be a lot more confident about committing it.
Comment #36
dinazaur commentedAs I said in title: it was a chunk of 300kb with an exception.. In our case that was not a big chunk. When Drupal sends big_pipe chunk using
printandflush()it is not sent immediately, it probably depends on the size of the data in the buffer (I have not made investigation of it). So our chunk (just the main menu of site) that was not rendered was sent at the last moment with a bunch of chunks. If it was sent alone it would be rendered for sure. So it may depend on the order and size of chunks -- random.Comment #37
dinazaur commentedI'm putting a large placeholder in tests because it's the most reliable way of reproducing this issue.
Comment #38
fjgarlin commentedhttps://git.drupalcode.org/project/drupal/-/merge_requests/4778, which is the MR version of #2 (with additional comments and tests), fixes the issue in my case, where I was getting a big block not being rendered through BigPipe.
I believe that all the feedback was addressed or explained. The tests come up green for DrupalCI and for GitLab CI.
I think it needs a rebase. Pending that rebase, this would have a +1 or RTBC from me.
Comment #42
catchThanks for the manual testing that is great. There is one comment early on in the other issue (#2) that claims the fix from here didn't work for them, but otherwise no-one has confirmed either way except @fjgarlin so let's go ahead and get this in.
I am not qualified to review the js here, but nod_ is and his feedback has been addressed.
Committed/pushed to 11.x, cherry-picked to 10.2.x, thanks!
Comment #43
catchComment #44
wim leersImpressive discovery, test coverage and fix! 🤩👏
Comment #45
wim leersComment #46
catch#3388913: Checkbox for Media library modal missing after search is another report related to this.