Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[HttpClient] CURLMOPT_PUSHFUNCTION breaks on CLI #57681

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
aleho opened this issue Jul 8, 2024 · 3 comments
Closed

[HttpClient] CURLMOPT_PUSHFUNCTION breaks on CLI #57681

aleho opened this issue Jul 8, 2024 · 3 comments

Comments

@aleho
Copy link
Contributor

aleho commented Jul 8, 2024

Symfony version(s) affected

6.4

Description

This weekend I was seeing quite a few crashes in an app processing URLs in messenger.

segfault at 20 ip 00007f96350cc937 sp 00007fffa3b202e8 error 6 in libc.so.6[7f9634fa0000+155000] likely on CPU 2 (core 1, socket 0)

Trying to reproduce the bug locally I started to see warnings on the console I've never encountered before. Apparently there was a change in an upstream service where they now push content in the Link: header since Friday.

Warning: PHP Request Shutdown: Cannot call the CURLOPT_PROGRESSFUNCTION in Unknown on line 0
PHP Warning:  PHP Request Shutdown: Cannot call the CURLOPT_PROGRESSFUNCTION in Unknown on line 0

How to reproduce

  • Inject HttpClientInterface into a command.
  • Request a URL where the server sends a Link: header.
  • Execute the command.

Possible Solution

As I don't need the resources at all (I'm only requesting the URL because I want to determine the content type, and sometimes the app gets an URL to HTML instead of e.g. images) my first idea was setting $maxPendingPushes to 0 which seems to be passed-through all the way from HttpClient to CurlClientState.

I couldn't find any documented option for that parameter (like, e.g. max_pending_pushes) so the only way I could come up with was to use a compiler pass.

<?php

declare(strict_types = 1);

namespace App\DependencyInjection;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class MaxPushesCompilerPass implements CompilerPassInterface
{
    #[\Override]
    public function process(ContainerBuilder $container): void
    {
        $container
            ->getDefinition('http_client.transport')
            ->setArgument('maxPendingPushes', 0);
    }
}

Additional Context

No response

@nicolas-grekas
Copy link
Member

The error tells about CURLMOPT_PROGRESSFUNCTION, what's the link with CURLMOPT_PUSHFUNCTION?

Can you please provide a full reproducer, with an actual endpoint that highlights this behavior?

@aleho
Copy link
Contributor Author

aleho commented Jul 18, 2024

I created a small demo app: https://github.com/aleho/httpclient-reproducer

Partly useless code because the issue is weird (e.g. fewer calls to the logger will not trigger any problems).


After debugging and trying to reliably reproduce this problem for hours (actually days) now I'm honestly at my wit's end. It's really hard to reproduce consistently outside of our somewhat larger project.

In the larger project as soon as [http_client] Queueing pushed response: "URL" is logged the shutdown warnings occur (and also the crashes on our server).

At the moment I only have one URL that is a public website out of my control (https://www.nockalmstrasse.at/de) leading to pushed responses being handled. I don't seem to be able to trigger the handler for pushed responses with my local Caddy setup and a simple endpoint with a Link header (with Caddy push and HTTP3 enabled and local certificates). For the demo I used Apache which is needed for HttpClient to handle pushed responses.

As soon as pushed response handling is disabled the error goes away, so that's why I thought it was related somehow. Also, if I disable the 'on_progress' function in TraceableHttpClient no warnings occur locally (because there's no callback at all).

Anyways, I'm really not sure how to reproduce it reliably. Just now I got another segfault locally in prod mode but the next run didn't crash at all. On our server the message queue crashed reliably every time it tried to handle the URL, locally only sometimes. Everything in the same container.

One of the cases I can reproduce it locally in the app linked above:

09:57:11 INFO      [http_client] Request: "GET https://www.nockalmstrasse.at/de"
09:57:11 DEBUG     [http_client] Queueing pushed response: "https://www.nockalmstrasse.at/static/fonts/gothambook-webfont.woff2"
09:57:11 DEBUG     [http_client] Queueing pushed response: "https://www.nockalmstrasse.at/static/fonts/gothammedium-webfont.woff2"
09:57:11 DEBUG     [http_client] Queueing pushed response: "https://www.nockalmstrasse.at/static/fonts/gothambold-webfont.woff2"
09:57:11 DEBUG     [http_client] Queueing pushed response: "https://www.nockalmstrasse.at/static/fonts/dm-serif-display-v15-latin-regular.woff2"
09:57:11 DEBUG     [http_client] Queueing pushed response: "https://www.nockalmstrasse.at/static/css/main.min.css"
09:57:11 DEBUG     [http_client] Queueing pushed response: "https://www.nockalmstrasse.at/static/js/main.modern.min.js"
09:57:11 INFO      [http_client] Response: "200 https://www.nockalmstrasse.at/de"
09:57:11 DEBUG     [app] Got text/html for https://www.nockalmstrasse.at/de
PHP Warning:  PHP Request Shutdown: Cannot call the CURLOPT_PROGRESSFUNCTION in Unknown on line 0

Warning: PHP Request Shutdown: Cannot call the CURLOPT_PROGRESSFUNCTION in Unknown on line 0

A call immediately after the previous one:

09:57:12 INFO      [http_client] Request: "GET https://www.nockalmstrasse.at/de"
09:57:12 DEBUG     [http_client] Queueing pushed response: "https://www.nockalmstrasse.at/static/fonts/gothambook-webfont.woff2"
09:57:12 DEBUG     [http_client] Queueing pushed response: "https://www.nockalmstrasse.at/static/fonts/gothammedium-webfont.woff2"
09:57:12 DEBUG     [http_client] Queueing pushed response: "https://www.nockalmstrasse.at/static/fonts/gothambold-webfont.woff2"
09:57:12 DEBUG     [http_client] Queueing pushed response: "https://www.nockalmstrasse.at/static/fonts/dm-serif-display-v15-latin-regular.woff2"
09:57:12 DEBUG     [http_client] Queueing pushed response: "https://www.nockalmstrasse.at/static/css/main.min.css"
09:57:12 DEBUG     [http_client] Queueing pushed response: "https://www.nockalmstrasse.at/static/js/main.modern.min.js"
09:57:12 INFO      [http_client] Response: "200 https://www.nockalmstrasse.at/de"
09:57:12 DEBUG     [app] Got text/html for https://www.nockalmstrasse.at/de

It also seems to be related to code changes or recompilation (locally). If I comment an unused portion of the code and execute the command I can reliably either produce the PHP warning or sometimes a segfault. Both with JIT and OPCache (+CLI) disabled or enabled.

@nicolas-grekas
Copy link
Member

Thanks for all the details. Let's disable PUSH by default, see #57870

nicolas-grekas added a commit that referenced this issue Jul 29, 2024
…l (nicolas-grekas)

This PR was merged into the 5.4 branch.

Discussion
----------

[HttpClient] Disable HTTP/2 PUSH by default when using curl

| Q             | A
| ------------- | ---
| Branch?       | 5.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Issues        | Fix #57681
| License       | MIT

I propose to disable HTTP/2 PUSH by default when using curl. Amp still supports it out of the box, but support in curl is too fragile (it segfaults, see linked issue).

We have to balance:
- seeing a perf downgrade for apps that might benefit from pushes
- vs putting every users at risk with a possible segfault when a server sends PUSH frames

Preventing this possible segfault is the most important here IMHO. Apps that leverage PUSH should be rare, and can re-enable if they really want to.

Commits
-------

ae9b889 [HttpClient] Disable HTTP/2 PUSH by default when using curl
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants