Problem/Motivation
The poll module displays forms in a #lazy_builder
. Apparently, that breaks Big Pipe when submitting that form because it can't deal with the EnforcedResponseException
that's then eventually thrown. And we can't just handle it, because we already started sending the response… no way we can now suddenly send a different one.
The reason this is a problem for Poll and not for e.g. the comment form (which is also rendered using a #lazy_builder
): because it has its own URL (route) to post to that doesn't use a #lazy_builder
: CommentForm
sets #action
. (See #3 + #4.) Eventually that wouldn't be necessary anymore, thanks to #2503429: [PP-*] Allow both AJAX and non-AJAX forms to POST to dedicated URLs, but that will only be a guarantee in D9 probably. (D8 must continue to work for forms that don't have their own route, for BC reasons.)
The problem is not the processing of the form. The form can be processed just fine (see #9 and preceding comments). The problem is that some forms' submit handlers result in a response (a redirect or otherwise). This triggers an EnforcedResponseException
, which then becomes the response that must be sent, but when BigPipe is already sending a response, and one of the placeholders being rendered ends up throwing that exception, that is physically impossible.
Proposed resolution
- Disable auto-placeholdering on POST requests (or actually, simply all unsafe requests). Because auto-placeholdering causes the form to be discovered later. We also disable render caching on unsafe methods. This is in line with that.
(A welcomed consequence of this, is that such forms are then "faster": because they're not auto-placeholdered anymore, they are rendered sooner, instead of as late as possible. So, consequently, the form is processed sooner (fewer things are rendered that will need to be thrown away anyway, because of the trumping response inEnforcedResponseException
), and hence the form's submit handler is executed faster.) - Consequently, it also makes sense to disable BigPipe for unsafe requests. In theory, we wouldn't need to, because there wouldn't be any placeholders, and hence BigPipe wouldn't be enabled. But, it's possible to manually create placeholders as well. Those manually created placeholders could also contain forms. Therefore, BigPipe should also be disabled for unsafe requests.
Once #2503429: [PP-*] Allow both AJAX and non-AJAX forms to POST to dedicated URLs lands, we can once again enable auto-placeholdering and BigPipe for unsafe requests.
Remaining tasks
Patch.- Reviews.
Tests.
User interface changes
None.
API changes
None.
Data model changes
None.
Comment | File | Size | Author |
---|---|---|---|
#24 | 2702609-23.patch | 12.19 KB | Wim Leers |
#24 | 2702609-23-tests-only-FAIL.patch | 8.15 KB | Wim Leers |
Comments
Comment #2
BerdirComment #3
Wim LeersOh, wow! Great edge case discovery! I can't believe nobody encountered that yet in the past six months! I'll need to step through this with a debugger to verify that it is indeed essential to disable BigPipe on POST requests. I'm not sure about that yet. But it definitely would be the simplest solution.
However, this is definitely not a problem for all forms. For example, for the comment form (e.g. at
/node/1
), it works fine. So, what makes Poll module's form handling special?Comment #4
Wim LeersReply to #3 from IRC:
That seems very plausible.
I still need/want to investigate this further before settling on the approach in #2. But if we end up going with the approach in #2, we should add:
I know there is an issue for that somewhere (to make sure all forms have dedicated controllers), but I can't seem to find it right now. I think @catch created it.
Comment #5
Wim Leerscatch just pointed me to the issue I was looking for: #2503429: [PP-*] Allow both AJAX and non-AJAX forms to POST to dedicated URLs. And #2699489: FormBuilder $ajax_form_request check does not check which AJAX form is being requested is related too.
Comment #6
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedTrain of thought
Yeah, while BigPipe is affected and leads to a bug here, the bigger issue is with
lazy buildersauto-placeholdering in general here.A block should just not be auto-placeholdered - if it contains a form without a dedicated URL to POST to.
While the normal processing for placeholders is still early enough to catch things like EnforcedFormException (?) or what that thing is called and specifically also does so (IIRC), it is just something that should in theory never be placeholdered at all.
However the block can perfectly be BigPipe'd and such Performance saved and as such just disabling auto-placeholdering also is wrong.
On the other hand, we disable most caching for non-safe methods for that exact reason (so that blocks and other things can be processed), so the patch is fine.
Once we have tackled the larger problem of dedicated FORM submissions urls, we can both activate caching and BigPipe again.
Therefore:
TLDR
Because all render caching is disabled for non-safe methods, the same should be true for BiPipe => RTBC.
Comment #7
Wim LeersThis still needs tests, at minimum. And the comment in #4.
Comment #8
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedCNW for tests and from IRC:
10:02 < Fabianx-screen> WimLeers: It is the right thing to do, we also disable all render caching for that reason.
10:03 < Fabianx-screen> WimLeers: Maybe we could also disable placeholdering, then BigPipe would not need a special case though.
I think it is better to disable (auto-)placeholdering in the Renderer directly, the same as we don't retrieve anything from caches for POST requests.
Performance gains come back automatically with dedicated URLs.
BigPipe could then still special case this or not ...
Comment #9
Wim LeersRender cache can indeed prevent a form from being rendered.
However, lazy builders do not prevent a form from being rendered. Lazy builders don't cause problems. In fact, that's what we are witnessing here: the form is being processed just fine!
The problem is that — just like Berdir already said in the IS — BigPipe calls lazy builders after the response has already begun being sent. And so if Form API throws this exception, it is by definition impossible to send this enforced response (contained in/passed by
\Drupal\Core\Form\EnforcedResponseException
), because the response is already being sent.Therefore I disagree with your analysis.
But I agree — like I already said in #3 and #4, that this is probably the right solution.
Comment #10
BerdirPolls as very much personalized and currently *always* use a lazy builder.
We actually have a dedicated url (poll/N) and we use that for form submissions, exactly because a form submission can be very slow when you have a lazy builder block on a big page manager page, for example.
I guess we could consider to not use a lazy builder on the detail page, not sure right now about the implications.
That said, I find the idea to not do placeholdering on post requests quite interesting. render caching is off anyway, and doing it immediately might actually improve performance because we find and process the form earlier in the request.
Comment #11
Wim LeersOh!!! Now that actually makes a ton of sense.
@Berdir++
@Berdir++
@Berdir++
But that'd require a change in the render system too, not just in BigPipe. We should have a big fat @TODO to the code that disables placeholdering on POST requests then. And of course, if we don't have placeholdering, then BigPipe automatically is made ineffective… (though we should still disable BigPipe explicitly on POST, because it's possible somebody is doing manual placeholdering).
So then we can basically retitle this issue to
, because that will be the primary change.Thoughts?
Comment #12
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commented#11: Hey, I want some of Berdir's credit, too for this :D.
Yes, that was the reasoning behind my idea / analysis.
It is just additional work to do.
And yes should disable in BigPipe, too for safety reasons.
Comment #13
Wim LeersFabianx++
Fabianx++
:)
There!
What do you mean by
? IMO the only reason to disable it for BigPipe explicitly as well is: .Comment #14
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commented#13: Yes, exactly because of manual placeholders.
Comment #15
Wim LeersOk, cool, then we're on the same page :)
Comment #16
Wim LeersMassive update to the issue summary.
Comment #17
Wim LeersComment #18
Wim LeersComment #19
Wim LeersHere's expanded test coverage for
BigPipeStrategy
.Comment #21
Wim Leers#19 had a random failure in the functional JS test. After re-testing, all is well again.
Comment #22
Wim LeersAnd here's the thing that prevents all placeholdering, both manual and automatic (and within automatic: both the direct and bubbled). i.e. this now avoids as many placeholders as possible.
Comment #23
Wim LeersComment #24
Wim LeersAnd now this is all done! :)
Comment #26
Fabianx CreditAttribution: Fabianx at Tag1 Consulting commentedAnd now its RTBC - Great work!
Comment #27
catchMinor but the @todo links to #2367555: Deprecate EnforcedResponseException support which is marked duplicate of #2503429: [PP-*] Allow both AJAX and non-AJAX forms to POST to dedicated URLs.
Also we can't resolve this @todo until we actually remove EnforcedResponseException, which is going to have to be in Drupal 9. I really hope we can get to the point sooner than later where these conditions never evaluate to true, but they'll have to sit there until support is removed.
Comment #28
Wim LeersThat is intentional. Then at least all links point to the same URL, rather than two different URLs, which is more confusing.
I'd be happy to update all existing links to point to the same URL (2503429), if you'd prefer that though.
Comment #29
catchI think linking to the active issue is better, and would prefer lack of consistency to making things consistently worse.
Either way the comment needs updating since that issue cannot remove those hunks anyway per #27.
Comment #30
Wim LeersThen all existing comments are wrong too: they all state that upon that issue landing, the additional checks can be removed, which per #27/#29 is not true. Shall I update all of the others as well then?
Comment #31
catchDiscussed with Wim in irc - I've re-opened the issue mark duplicate to be about deprecating the functionality, which makes the comment correct and we needed an issue for that anyway.
Comment #32
catchCommitted/pushed to 8.2.x and cherry-picked to 8.1.x. Thanks!
Comment #34
Wim LeersAlso committed & pushed to the contrib module for Drupal 8.0: http://drupalcode.org/project/big_pipe.git/commit/2331bf5
Comment #35
Wim LeersTurns out the changes we made to auto-placeholdering in fact also were necessary for AJAXy forms in lazy builders to work also without BigPipe: #2710939: AddToCartFormatter lazy builder causes erroneous Ajax submits, when a second form is present in Commerce for D8 was fixed by this :)
Comment #36
Wim LeersRetroactively tagging contributed project blocker: blocked both Poll & Commerce.
Comment #37
catchIn case anyone gets a similar error but it's not fixed by this patch, there's a fair chance that it might be #2699489: FormBuilder $ajax_form_request check does not check which AJAX form is being requested instead which has similar symptoms but a different cause.
Comment #38
Wim LeersComment #39
Wim LeersOops, that was already linked apparently.
Comment #41
woprrr CreditAttribution: woprrr as a volunteer commentedSame issue return in #2723407: Not compatible with 8.1.1 big_pipe but seems fixed with a patch on BigPipe.
Comment #42
BerdirSomehow I have a test fail in simplenews that I tracked down to this commit with git bisect. Can't quite explain it yet, but somehow form validation messages do not show up anymore for my form.
See https://www.drupal.org/pift-ci-job/307415, I did not yet create an issue for this.
Comment #43
Wim Leers#41: Great.
#42: That's … bizarre. Can you open a new issue and STR?
Comment #44
Wim Leers#42: see #2712935-7: Messages are not rendered last, meaning those messages set within placeholders only appear on the next request, I'm fairly certain that's what you're seeing.