Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers created an issue. See original summary.

Wim Leers’s picture

Version: 8.2.x-dev » 8.1.x-dev
Wim Leers’s picture

I gave this another look.

I installed the devel module and its kint submodule. Then I did this:

diff --git a/core/modules/quickedit/quickedit.module b/core/modules/quickedit/quickedit.module
index 2d69a66..13260fb 100644
--- a/core/modules/quickedit/quickedit.module
+++ b/core/modules/quickedit/quickedit.module
@@ -42,6 +42,7 @@ function quickedit_help($route_name, RouteMatchInterface $route_match) {
  * in-place editing' permission.
  */
 function quickedit_page_attachments(array &$page) {
+  kint('this is a test');
   if (!\Drupal::currentUser()->hasPermission('access in-place editing')) {
     return;
   }

While BigPipe is enabled, the end result looks like this:

If you're using settings.local.php, which you should if you're developing, then you'll have $config['system.logging']['error_level'] = 'verbose';, and that will make the exact same thing look like this:

However, disable BigPipe, and then it'll look like this:


It'd be easy to blame BigPipe. The real root cause however is not BigPipe, but either:

  1. Kint's assumption that it's always okay to dump data
  2. Symfony's HTTP kernel: our index.php is as barebone as it can be, and it's equivalent with Symfony's: https://github.com/symfony/symfony-standard/blob/master/web/app.php — the problem here is that $response->send() is called after the session has already been closed in $kernel->handle($request), and BigPipe needs the session while sending the response
  3. However, you can just as easily blame StackPHP in general and Drupal 8's \Drupal\Core\StackMiddleware\Session session middleware instead, because this is what is saving (and closing) the session
  4. But then you might as well blame Symfony's \Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage::save(), which is what is effectively closing the session.
  5. Or you might blame \Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage::start(), which checks if headers are already sent and then causes the fatal error you see in the screenshots, even though no headers need to be sent, because we're merely reopening the same session

In other words, this shows that neither Kint, nor Symfony's HTTP kernel, nor Symfony's session handling, nor StackPHP are designed with streamed responses in mind.

A nearly identical issue exists with using phpseclib + any Symfony app: https://github.com/symfony/symfony/issues/18819 — but since in that case the output is not intentional, but an error, it's easy to work around. In the case of kint, it's very much intentional.

Note that in searching for "Symfony session header_sent", I actually stumbled upon #2597520: Don't save the session before $response->send(), which is where I was trying to change session handling, but that attempt failed. None of the session system experts there suggested a better approach.

Finally, note that the kint() output worked! It's just that it broke the rest of the page. For the above reasons.


The above is what happens if you call kint() before any output has started. But when using kint() while rendering the page, (i.e. after initial HTML has already been sent) it works fine, with or without BigPipe:

To get this, I reverted the change above, and instead made this change:

 core/lib/Drupal/Core/Render/Element/StatusMessages.php | 1 +
 1 file changed, 1 insertion(+)

diff --git a/core/lib/Drupal/Core/Render/Element/StatusMessages.php b/core/lib/Drupal/Core/Render/Element/StatusMessages.php
index f026aa5..dea563a 100644
--- a/core/lib/Drupal/Core/Render/Element/StatusMessages.php
+++ b/core/lib/Drupal/Core/Render/Element/StatusMessages.php
@@ -70,6 +70,7 @@ public static function generatePlaceholder(array $element) {
    * @see drupal_get_messages()
    */
   public static function renderMessages($type) {
+    kint('this is a test');
     // Render the messages.
     return [
       '#theme' => 'status_messages',

In other words:

  1. using kint() after initial content has been sent works fine
  2. using kint() before any content has been sent breaks BigPipe's streamed responses, but the Kint output itself works fine. Solving this requires untangling many layers of interdependencies in the session handling system in Symfony, which was never designed to support interrupted streamed responses.
  3. That's right, using kint() means potentially interrupting a stream. Which means the above behavior is pretty much by design! It's annoying. But blame kint(), Symfony or even PHP (for not supporting streamed responses better), but there is unfortunately very little BigPipe can do.

(In the course of writing this, I tried to at least a dozen different hacks to get this to work anyway. And I failed.)

mondrake’s picture

Thanks for all reasearch and efforts, @Wim Leers

Wim Leers’s picture

You're welcome!

Have you seen the problem you reported at #2678662-10: Ensure BigPipe does not break when HTML document contains CDATA sections or inline scripts matching certain patterns many times since you reported it 4 months ago?

mondrake’s picture

#5 actually in my dev I just disabled BigPipe, sorry, and never looked at that again. Once I find some time I'll check what the situation is now.