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 user: admin, pass: admin) it will be almost always not rendered.
I thought Simplitest will work more than one day.

Proposed resolution

So there are a lot of possible solutions. I'll attach two patches for the 1-st and 2-st solutions.

  1. Is to track all nodes that have invalid JSON
        if (response === false) {
          HERE WE PUT A NODE INTO AN ARRAY
          return;
        }
    

    And on DOMContentLoaded process them all.

    With such an approach we are sure that everything will be rendered but at the end of the page loading.

  2. 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: true configuration 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 inside script placeholder node.)
    • Rework a bit how we process mutations so it will also track changed made to characterData mutation 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.

  3. 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

-

Issue fork drupal-3387039

Command icon 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

dinazaur created an issue. See original summary.

dinazaur’s picture

Patch with #1 solution.

dinazaur’s picture

StatusFileSize
new2.51 KB

Patch with #2 solution

dinazaur’s picture

StatusFileSize
new1.44 MB

Patch with Functional test that does not work.

dinazaur’s picture

Assigned: dinazaur » Unassigned
Status: Active » Needs review
abramm’s picture

Hi @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?

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Bug Smash Initiative, +Needs tests

Thank you for reporting.

Can we get a test only patch showing the issue.

dinazaur’s picture

Status: Needs work » Needs review

Please read the summary.

I tried to implement a test for this bug, but could not reproduce it inside headless Chromium, probably the problem is probably that it does not work in the same way as not headless browser. I'll attach a patch with the test if someone wants to play with it, but I'm not sure if that's possible to reproduce it inside Functional tests.

From what I understand it's impossible to implement a test for this.

smustgrave’s picture

Could tests not be added like BigPipeStrategyTest

dinazaur’s picture

I 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.

dinazaur’s picture

smustgrave’s picture

Ah okay I’ll leave for another reviewer then to see what they think. Don’t want to mark anything without test coverage personally.

dinazaur’s picture

Issue summary: View changes

dinazaur’s picture

The PR contains solution #2 because it seems to be the appropriate way of fixing the issue.

dinazaur’s picture

Issue summary: View changes
dinazaur’s picture

StatusFileSize
new592.83 KB

Okay, I actually managed to consistently reproduce the bug on my local machine, hopefully, it will fail now inside Drupal CI.

dinazaur’s picture

StatusFileSize
new781.13 KB

Fixed cspell error.

Status: Needs review » Needs work

The last submitted patch, 18: 3387039-big-pipe-large-content-test-18.patch, failed testing. View results

dinazaur’s picture

Issue tags: -Needs tests

Yep, 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.

dinazaur’s picture

Status: Needs work » Needs review
smustgrave’s picture

To save space what do you think of doing

<div id="big-pipe-large-content">
  {% for i in 0..1000000 %}
    boing
  {% endfor %}
</div>

In the template.

Also hiding the test-only patch so the review bot doesn't pick it up.

dinazaur’s picture

Nice 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.

dinazaur’s picture

Issue summary: View changes

Status: Needs review » Needs work

The last submitted patch, 23: 3387039-big-pipe-large-content-test-23.patch, failed testing. View results

dinazaur’s picture

Status: Needs work » Needs review

Nice, it failed.

lysenko’s picture

Status: Needs review » Reviewed & tested by the community

+1 RTBC

catch’s picture

ralphmoser’s picture

I 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.

catch’s picture

Priority: Major » Critical

Bumping this to critical - bits of the page just randomly go missing rendering the site potentially unusable, multiple reports in what look like duplicate issues.

nod_’s picture

+1 on the approach, could use a couple of comments for future us.

catch’s picture

Status: Reviewed & tested by the community » Needs work

Needs work to address @nod_'s feedback.

dinazaur’s picture

Status: Needs work » Needs review

Added additional comments to the code, ready for review.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Hiding patches to make it easier to show work is in the MR.

Appears the threads and feedback have been addressed.

catch’s picture

Status: Reviewed & tested by the community » Needs review
Issue tags: +Needs manual testing

I 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.

dinazaur’s picture

which seems to be more about lots of placeholders rather than large placeholders

As 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 print and flush() 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.

dinazaur’s picture

I'm putting a large placeholder in tests because it's the most reliable way of reproducing this issue.

fjgarlin’s picture

Status: Needs review » Reviewed & tested by the community

https://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.

  • catch committed 09bcf41f on 10.1.x
    Issue #3387039 by dinazaur, smustgrave, fjgarlin, nod_: Large...

  • catch committed cd83c673 on 10.2.x
    Issue #3387039 by dinazaur, smustgrave, fjgarlin, nod_: Large...

  • catch committed 6f97533d on 11.x
    Issue #3387039 by dinazaur, smustgrave, fjgarlin, nod_: Large...
catch’s picture

Thanks 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!

catch’s picture

Version: 11.x-dev » 10.1.x-dev
Status: Reviewed & tested by the community » Fixed
Issue tags: -Needs manual testing
wim leers’s picture

Impressive discovery, test coverage and fix! 🤩👏

wim leers’s picture

Issue tags: +JavaScript
catch’s picture

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.