Post-response task running (destructable services) are actually blocking; add test coverage and warn for common misconfiguration

Created on 13 July 2022, almost 2 years ago
Updated 20 December 2023, 6 months ago

Problem/Motivation

Drupal core and a number of contributed modules expect to perform operations after the HTTP response is sent to the client, but prior to script termination. Examples are automated cron, storing JSON:API normalizations to the cache, and any operations that are deferred to the "destruction" of a service by implementing DestructableInterface.

In validating my implementation of the latter case (deferring sending push notifications) I noticed that client receipt of response contents were blocked until all code paths from index.php were executed.

I found #2579775: Missing Content-Length header blocks response until all registered shutdown functions execute and the Kernel terminate events run which is related to a similar symptom of blocking the response to the client side, but I think that is more sensitive to certain backends (e.g., FastCGI.) In any event, applying a re-rolled version of the PoC patch on that issue did not resolve the issue of blocked requests.

By way of level-setting, PHP can be set to have output buffering turned on or off by default through a PHP ini setting. Drupal does not explicitly start output buffering, e.g. by calling ob_start(), though such a thing was discussed back in Drupal 5 days but never went in to core.

Response::send(), which is called in Drupal's front matter, does flush the output buffer, if one is present. In my case (PHP run through Apache's mod_php - I'm not looking to debate your love of FPM here) I do not have output buffering on by default, so ob_end_flush() is never called.

Even if I add an explicit ob_start() before $response->send() in index.php, the output buffer is flushed but the PHP "backend" (I'm using the PHP docs terminology here) does not flush its response buffer to the client side. I need to further call flush() to do so.

All of this seems a little "too obvious" to me for this to be an oversight or purposeful omission on Drupal core's part, but this is:

  1. hard to test through an automated regime/I don't think we have test coverage for kernel termination/DestructableInterface, and
  2. These features are rather expert-level and the symptoms of this not working as advertised are easy to miss (your request just takes a little longer than it should.

Users of automated cron are probably most affected, though again, power users are less likely to use it.

Steps to reproduce

Perform a request with an implementation of DestructableInterface to do work after the response is sent to the client. Note that the response is blocked by work that is performed downstream of $kernel->terminate().

Proposed resolution

Call flush() in DrupalKernel::terminate(). Or perhaps do this in a stack middleware, though that seems a little heavy-handed. Alternatively, call it direclty in index.php?

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

🐛 Bug report
Status

Fixed

Version

11.0 🔥

Component
System 

Last updated 1 day ago

No maintainer
Created by

🇺🇸United States bradjones1 Digital Nomad Life

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

Production build 0.69.0 2024