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

  1. 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 in EnforcedResponseException), and hence the form's submit handler is executed faster.)
  2. 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.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Berdir created an issue. See original summary.

Berdir’s picture

Status: Active » Needs review
FileSize
800 bytes
Wim Leers’s picture

Title: Don't use big pipe delivery on POST requests » Don't use BigPipe delivery on POST requests
Priority: Normal » Major

Oh, 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?

Wim Leers’s picture

Reply to #3 from IRC:

14:06:24 <WimLeers> berdir: follow-up Q: https://www.drupal.org/node/2702609#comment-11051647
14:12:16 <berdir> WimLeers: that's a good question, I don't know
14:13:47 <berdir> WimLeers: ah, might have an idea. comment posts to a special route, where it doesn't use a lazy_builder
14:14:00 <bojanz> berdir: reviewed, looks good. That was a tough one :)
14:14:21 <berdir> WimLeers: see CommentForm, where it sets #action. I'd guess that if for some reason that condition isn't triggered, then it's broken too

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:

// @todo Remove the isMethodSafe() condition as soon as Drupal gets rid of \Drupal\Core\Form\EnforcedResponseException.

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.

Fabianx’s picture

Title: Don't use BigPipe delivery on POST requests » Disable BigPipe delivery for non-safe method (e.g. POST) requests like render caching
Status: Needs review » Reviewed & tested by the community

Train of thought

Yeah, while BigPipe is affected and leads to a bug here, the bigger issue is with lazy builders auto-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.

Wim Leers’s picture

Status: Reviewed & tested by the community » Needs work

This still needs tests, at minimum. And the comment in #4.

Fabianx’s picture

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

Wim Leers’s picture

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.

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

Berdir’s picture

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

Wim Leers’s picture

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.

Oh!!! 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 Disable auto-placeholdering on POST requests to help find forms as fast as possible., because that will be the primary change.

Thoughts?

Fabianx’s picture

#11: Hey, I want some of Berdir's credit, too for this :D.

Yes, that was the reasoning behind my idea / analysis.

No placeholdering => We find the form faster => We do not profit from placeholdering anyway when all render caching is off.

It is just additional work to do.

And yes should disable in BigPipe, too for safety reasons.

Wim Leers’s picture

Fabianx++
Fabianx++

:)

There!


And yes should disable in BigPipe, too for safety reasons.

What do you mean by safety reasons? IMO the only reason to disable it for BigPipe explicitly as well is: because manual placeholdering.

Fabianx’s picture

#13: Yes, exactly because of manual placeholders.

Wim Leers’s picture

Ok, cool, then we're on the same page :)

Wim Leers’s picture

Title: Disable BigPipe delivery for non-safe method (e.g. POST) requests like render caching » Disable auto-placeholdering (and BigPipe) on unsafe requests to help find forms as fast as possible (to allow EnforcedResponses to work)
Issue summary: View changes

Massive update to the issue summary.

Wim Leers’s picture

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
2.12 KB
Wim Leers’s picture

FileSize
7.92 KB
5.87 KB

Here's expanded test coverage for BigPipeStrategy.

Status: Needs review » Needs work

The last submitted patch, 19: 2702609-19.patch, failed testing.

Wim Leers’s picture

Status: Needs work » Needs review

#19 had a random failure in the functional JS test. After re-testing, all is well again.

Wim Leers’s picture

Title: Disable auto-placeholdering (and BigPipe) on unsafe requests to help find forms as fast as possible (to allow EnforcedResponses to work) » Disable placeholdering (and BigPipe) on unsafe requests to help find forms as fast as possible (to allow EnforcedResponses to work)
Issue summary: View changes
Issue tags: -Needs tests
FileSize
12.01 KB
4.49 KB

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

Wim Leers’s picture

Issue tags: +D8 cacheability
Wim Leers’s picture

And now this is all done! :)

The last submitted patch, 24: 2702609-23-tests-only-FAIL.patch, failed testing.

Fabianx’s picture

Status: Needs review » Reviewed & tested by the community

And now its RTBC - Great work!

catch’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/lib/Drupal/Core/Render/PlaceholderingRenderCache.php
@@ -92,6 +92,11 @@ public function __construct(RequestStack $request_stack, CacheFactoryInterface $
   public function get(array $elements) {
+    // @todo remove this check when https://www.drupal.org/node/2367555 lands.
+    if (!$this->requestStack->getCurrentRequest()->isMethodSafe()) {
+      return FALSE;
+    }
+
     // When rendering placeholders, special case auto-placeholdered elements:

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

Wim Leers’s picture

Status: Needs work » Reviewed & tested by the community

Minor but the @todo links to #2367555] which is marked duplicate of #2503429: [PP-*] Allow both AJAX and non-AJAX forms to POST to dedicated URLs.

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

catch’s picture

Status: Reviewed & tested by the community » Needs work

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

Wim Leers’s picture

Then 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?

catch’s picture

Status: Needs work » Reviewed & tested by the community

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

catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.2.x and cherry-picked to 8.1.x. Thanks!

  • catch committed 0fcef5b on 8.2.x
    Issue #2702609 by Wim Leers, Berdir, Fabianx: Disable placeholdering (...
Wim Leers’s picture

Also committed & pushed to the contrib module for Drupal 8.0: http://drupalcode.org/project/big_pipe.git/commit/2331bf5

Wim Leers’s picture

Turns 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 :)

Wim Leers’s picture

Retroactively tagging contributed project blocker: blocked both Poll & Commerce.

catch’s picture

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

Wim Leers’s picture

Related issues:
Wim Leers’s picture

Related issues:

Oops, that was already linked apparently.

Status: Fixed » Closed (fixed)

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

woprrr’s picture

Same issue return in #2723407: Not compatible with 8.1.1 big_pipe but seems fixed with a patch on BigPipe.

Berdir’s picture

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

Wim Leers’s picture

#41: Great.

#42: That's … bizarre. Can you open a new issue and STR?

Wim Leers’s picture