Problem/Motivation
On a website with big pipe enabled we encountered an issue where every now and then a big pipe placeholder was not being replaced.
To reproduce the issue I refreshed a page containing a big pipe placeholder multiple times with the browser console open. After some 30-50 refreshes I hit the following:
SyntaxError: JSON.parse: unterminated string at line 1 column 16683 of the JSON data
With stacktrace:
bigPipeProcessPlaceholderReplacement
each
each
bigPipeProcessDocument
bigPipeProcess/timeoutID
Then I checked big_pipe.es6.js and found the comment:
// If we try to parse the content too early (when the JSON containing Ajax
// commands is still arriving), textContent will be empty which will cause
// JSON.parse() to fail. Remove once so that it can be processed again
// later.
// @see bigPipeProcessDocument()
I tracked down this addition to #2469431-148: BigPipe for auth users: first send+render the cheap parts of the page, then the expensive parts.
I don't believe however that just checking textContent is enough to ensure the output of the big pipe replacement has been received completely.
Proposed resolution
(Description of the proposed solution, the rationale behind it, and workarounds for people who cannot use the patch.)
Remaining tasks
(reviews needed, tests to be written or run, documentation to be written, etc.)
User interface changes
None
API changes
None
Data model changes
None
Comment | File | Size | Author |
---|---|---|---|
#17 | 2931443-17.patch | 3.32 KB | Wim Leers |
#17 | interdiff-15-17.txt | 2.17 KB | Wim Leers |
#15 | 2931443-15.patch | 3.45 KB | Wim Leers |
#15 | interdiff-13-15.txt | 3.87 KB | Wim Leers |
#13 | core-big-pipe-parsing-2931443-13.patch | 1.42 KB | nils.destoop |
Comments
Comment #2
casey CreditAttribution: casey at SWIS commentedComment #3
casey CreditAttribution: casey at SWIS commentedComment #4
borisson_I don't understand why #3 isn't tested by the bot. Adding the needs tests tag. But if this only happens after ~30 requests it will be really complicated to write a good test for this.
Is this just a rename for clarity? Because if it's not needed, I think we should keep it as it was. To make it easier to see the actual changes here.
Comment #5
casey CreditAttribution: casey at SWIS commented#3 is a patch for Drupal 8.3 (so we could apply it to the project in which we encountered the issue).
Comment #6
Wim LeersThis sounds indeed plausible. Probably this was fine for the less complex/elaborate cases we tested, because we didn't have >16 KB of data to arrive via JSON, and you do (since you report that it fails at
column 16683
).I like this!
#4: It doesn't happen every 30 requests for certain. This is a client-side (browser) race condition. We may not be able to write test coverage for it. But we definitely should try. Something like sending 16 KB of data, but sleeping for a second between every 1 KB would hopefully trigger it.
#5: this patch needs to be rolled against 8.5 for it to get committed. Also, no bug fixes happen for 8.3 anymore. They will happen for 8.4.
Comment #7
Wim LeersComment #8
nils.destoop CreditAttribution: nils.destoop as a volunteer and at Wunder commentedI had similar issues with my project. Chrome was ok, but firefox was complaining. Manually applying the patch on the 8.4.4 fixed the issue.
Comment #9
Wim Leers@zuuperman Thank you very much for confirming! Any chance you'd be up for writing test coverage as I described in #6? :) I'd promise fast reviews!
Comment #11
nils.destoop CreditAttribution: nils.destoop as a volunteer and at Wunder commentedSry, Wim. Did not have time.
But an update. I started noticing issues when a block was not cached yet. Some blocks randomly returned empty data, leading to empty blocks displayed. Removing the patch fixes this, but brings back my firefox problem. So the patch is not completely ok.
The moment blocks are cached, the empty block is gone and big pipe replacement behaves normally.
Comment #12
nils.destoop CreditAttribution: nils.destoop as a volunteer and at Wunder commentedI created a patch that works for my project. To be 100% sure, I reverted a couple of times to the original patch. Every time I do, one of the blocks is not displayed. After some debugging I noticed it's related with following code:
The block that is going wrong, is always a block that comes after a form that has autocomplete enabled. Viewing with inspector
So the previousElementSibling is not the replacement data. See my console.log of placeholderReplacement and the found response:
Comment #13
nils.destoop CreditAttribution: nils.destoop as a volunteer and at Wunder commentedIncluded a patch that works for me, without the use of replacement signal.
Optionally we could tweak the replacement signal version, by searching the previous elements by id. But this leads to dom queries that can be avoided.
Comment #14
GrandmaGlassesRopeManThis should follow our standards for multi-line comments.
This is a bit confusing. Perhaps some additional documentation that we're going to return early, if JSON parsing fails?
Comment #15
Wim Leers@zuuperman++
@zuuperman++
@zuuperman++
Thank you so much for your detailed analysis and updated patch, that helps a lot! Your patch makes sense too :)
I addressed #14, and in doing so, I realized this could become a lot clearer by refactoring some of the existing code. Basically, the existing if/else makes sense, but this found another possible situation to go into the "if" branch. By moving the condition logic for the "if" branch in a helper method, we can make this much easier to understand.
Curious to hear what you think @drpal.
Comment #16
GrandmaGlassesRopeManSome minor feedback,
Multiline comment format.
This should be
@return
instead of@returns
You don't need an
else{}
block here since you are returning inside the if.Comment #17
Wim LeersThanks! Done! ✔️
Comment #18
Wim LeersSo …
…But this bug only occurs in a browser/network race condition.
I could try to trigger it manually by completely hacking the server side to send A) a huge JSON payload, B) cut that JSON payload in half and send the first half, then pause for 2 seconds to hopefully trigger the browser, then send the second half. But that is a LOT of work and likely it would still not reliably test this (read: random test failure).
I can manually send malformed JSON and test it that way (also lots of work, but less crazy brittle). But it'd still not be testing what we actually want to test: we'd not be testing the race condition, but a branch in the JS code because we know that that JS code branch exists, i.e. whitebox testing rather than blackbox testing, i.e. an integration test testing unit behavior.
So, even though I want BigPipe to have as-solid-as-possible test coverage, and a regression test for every bug, I don't think that's actually reasonable and feasible here. Removing
.Comment #19
GrandmaGlassesRopeMan@Wim Leers ➕1️⃣
Comment #20
borisson_I originally added the needs tests tag, and I agree with the reasoning in #18. Great job Wim!
Comment #23
catchCommitted/pushed to 8.6.x and cherry-picked to 8.5.x. Thanks!