php / php-src

The PHP Interpreter
https://www.php.net
Other
38.09k stars 7.74k forks source link

connection_status and connection_aborted does not work #10106

Open seltix5 opened 1 year ago

seltix5 commented 1 year ago

From manual page: https://php.net/function.connection-status

Hi, I tested this using PHP 7.4 but it does not detect disconnect, I try to stop and close the browser.

<?php
ignore_user_abort(true);
set_time_limit(0);

while(1)
{
    echo "\n";
    ob_flush();
    flush();

    // Did the connection fail?
    if(connection_status() != CONNECTION_NORMAL)
    {
        break;
    }

    sleep(1);
}

cmb69 commented 1 year ago

Did you mean to report this as actual bug, or as documentation issue?

seltix5 commented 1 year ago

hi, I supose if the code is as it should and there is no documented reasons for this to not work, it is a bug

( sorry for delay, did not get the a notification =s )

stramunin commented 1 year ago

@seltix5 What should not work?

ignore_user_abort(true);
set_time_limit(0);
error_log('start');
while(1)
{
    echo "\n";
    ob_flush();
    flush();

    // Did the connection fail?
    if(connection_status() != CONNECTION_NORMAL)
    {
        break;
    }

    sleep(1);
}
error_log('stop');

Built-in web-server. I open the browser and close after a few seconds.

[Thu Dec 15 14:36:42 2022] 127.0.0.1:52133 Accepted
[Thu Dec 15 14:36:42 2022] start
[Thu Dec 15 14:36:48 2022] stop
[Thu Dec 15 14:36:48 2022] 127.0.0.1:52133 [200]: GET /
[Thu Dec 15 14:36:48 2022] 127.0.0.1:52133 Closing
cmb69 commented 1 year ago

Indeed, this works as expected with the built-in Webserver.

Which SAPI do you use, @seltix5? Also note that PHP 7.4 is no longer supported, so please check with any of the actively supported PHP versions.

stramunin commented 1 year ago

Checked with php-fpm, also works.

seltix5 commented 1 year ago

hello, I just tested with PHP Version 8.1.13 and it does not work for me either, I never get the 'stop' output after stoping the request. I tested this with chrome.

Server API : LiteSpeed V8.1 Cloudlinux 1.3 (btw, my host is on a shared server in case that matters)

cmb69 commented 1 year ago

I never get the 'stop' output after stoping the request.

Well, no output is supposed to be shown in the browser after you've closed the tab.

Anyhow, there might be an issue with the lsapi.

seltix5 commented 1 year ago

lol, output in the log file after the 'start' line. I got the 'start' line but never got the 'stop' after stoping the request.

image

what should I do then? report to LiteSpeed devs? ( https://www.litespeedtech.com/support/forum/forums/bug-reports.9/ )

stramunin commented 1 year ago

@seltix5 First, try a different webserver if possible and post what happens.

KapitanOczywisty commented 1 year ago

I've tested that on LiteSpeed V8.0.1 (Cloudlinux 1.3) and seems like php process is just killed.

2022-12-15 15:04:00.397122 [NOTICE] [145821] [T0] [xxxx:xxxx#APVH_www.xxxx] [STDERR] start
2022-12-15 15:04:04.441395 [INFO] [145821] [T0] [xxxx:xxxx#APVH_www.xxxx] Abort request processing by PID:180596, kill: 1, begin time: 4, sent time: 4, req processed: 0

@seltix5 can you run following code?

<?php
error_reporting(-1);
ini_set('display_errors', '1');
ignore_user_abort(true);
set_time_limit(30);

register_shutdown_function(function() {error_log('shutdown');});

error_log('start');
for($i = 0; $i < 20; ++$i)
{
    echo "\n";
    ob_flush();
    flush();

    // Did the connection fail?
    if(connection_status() != CONNECTION_NORMAL)
    {
error_log('abort');
        break;
    }

    sleep(1);
}
error_log('loop end');
stramunin commented 1 year ago

@KapitanOczywisty I think by setting set_time_limit the sleep duration will be ignored. There must be another reason why the script is aborted.

KapitanOczywisty commented 1 year ago

I think by setting set_time_limit the sleep duration will be ignored.

It'll not. TBH set_time_limit doesn't matter in the last script at all.

There must be another reason why the script is aborted.

I'm not familiar with LiteSpeed, but from what I've gathered there is an option in server configuration to kill script when client aborts connection. Original script was flawed so we cannot tell for sure if connection_status returned wrong value and script looped indefinitely or script was killed as soon as connection was closed. Maybe Litespeed is also checking if process is sleeping and because of that it is killed. Any of that is only my speculation.

seltix5 commented 1 year ago

hi, the process is not killed when I click stop

ignore_user_abort(true);
set_time_limit(0);
error_log('start');
while(1)
{
    echo "\n";
    ob_flush();
    flush();

    // Did the connection fail?
    if(connection_status() != CONNECTION_NORMAL)
    {
        break;
    }

    sleep(5);
    error_log('loop, status: ' . connection_status());
}
error_log('stop');
[15-Dec-2022 15:28:54 UTC] start
[15-Dec-2022 15:28:59 UTC] loop, status: 0
[15-Dec-2022 15:29:04 UTC] loop, status: 0
[15-Dec-2022 15:29:09 UTC] loop, status: 0
[15-Dec-2022 15:29:14 UTC] loop, status: 0
[15-Dec-2022 15:29:19 UTC] loop, status: 0
[15-Dec-2022 15:29:24 UTC] loop, status: 0
[15-Dec-2022 15:29:29 UTC] loop, status: 0
[15-Dec-2022 15:29:34 UTC] loop, status: 0
[15-Dec-2022 15:29:39 UTC] loop, status: 0
KapitanOczywisty commented 1 year ago

What sapi is php running? var_dump(php_sapi_name());

seltix5 commented 1 year ago

What sapi is php running? var_dump(php_sapi_name());

its the same server

Server API : LiteSpeed V8.1 Cloudlinux 1.3

seltix5 commented 1 year ago

I just tested on another server that I ask access to do this test and the problem was the same but it does not have PHP 8 available. (PHP Version 7.4.33, SAPI FPM/FastCGI) I will try to get access to others servers to test this.

stramunin commented 1 year ago

I think the problem may be elsewhere, because you tried php-fpm.

seltix5 commented 1 year ago

I published this for testing. Server: PHP Version 8.1.13, LiteSpeed V8.1 Cloudlinux 1.3 (the log will be saved by ip to avoid conflicts)

https://00351.net/test.php

<?php
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
    $ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
    $ip = $_SERVER['REMOTE_ADDR'];
}

ini_set("error_log", __DIR__ . '/error_log-' . $ip);

ignore_user_abort(true);
set_time_limit(0);
error_log('start PHP ' . phpversion() . ' - SAPI ' . php_sapi_name());
$i = 0;
for ($i = 0; $i < 10; $i++) {
    echo str_repeat("\n", 5000);
    ob_flush();
    flush();

    // Did the connection fail?
    if (connection_status() != CONNECTION_NORMAL) {
        error_log('break!');
        break;
    }

    sleep(1);
    error_log('loop ' . $i . ', status: ' . connection_status());
}
error_log('stop');
echo 'stop!';

https://00351.net/log.php

<?php
if (isset($_GET['phpinfo'])) {
    echo 'phpinfo';
    echo phpinfo();
}

if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
    $ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
    $ip = $_SERVER['REMOTE_ADDR'];
}

ini_set("error_log", __DIR__ . '/error_log-' . $ip);

echo 'error_log=', ini_get('error_log'), '<br>';
if (file_exists('error_log-' . $ip))
    echo str_replace("\n", '<br>', file_get_contents('error_log-' . $ip));
seltix5 commented 1 year ago

PS: this host account is empty, there is no php.ini or any other files

image

stramunin commented 1 year ago

Sorry, but I'm afraid to follow unfamiliar links. :)

seltix5 commented 1 year ago

Not sure what demoniac virus you can catch just opening the link but sure, i'm just providing whatever I can to help understand the problem and for future reference the code provided may help. If it works on some cases there must be a difference somewhere And the "host is empty" message was not to say "its safe click here muahaha", the point was to say there is no external files changing php settings or whatever else may cause the problem

stramunin commented 1 year ago

I understand you. I wanted to clarify the points that we know:

Right?

KapitanOczywisty commented 1 year ago

@seltix5 Litespeed is behind nginx, and that complicates things. Sending one character (echo "\n";) may not be enough to overflow the nginx proxy buffer (if is enabled) or it's not aborting internal connection when client disconnects. You can try to send more characters in loop (like 4k or more), but this is not looking like php issue and we won't be able to help much further.

gwanglst commented 1 year ago

When PHP engine is not embedded in a web server, like LiteSpeed and FPM, it communicates with web server over an IPC connection, not directly associated with client connection, the connection_status is the status of the IPC connection.

seltix5 commented 1 year ago

@seltix5 Litespeed is behind nginx, and that complicates things. Sending one character (echo "\n";) may not be enough to overflow the nginx proxy buffer (if is enabled) or it's not aborting internal connection when client disconnects. You can try to send more characters in loop (like 4k or more), but this is not looking like php issue and we won't be able to help much further.

hi, I try with 5k (code updated https://github.com/php/php-src/issues/10106#issuecomment-1353379675) but did not work either.

there is no solution for this then? :(

KapitanOczywisty commented 1 year ago

I've installed LiteSpeed V8.0.1 on docker and connection_status() works as expected, so this is an issue with nginx reverse proxy. You could contact hosting provider, they probably can tweak some nginx settings.

seltix5 commented 1 year ago

I will try, hopefully they may know what setting is causing the problem.

stramunin commented 1 year ago

With nginx+php-fpm everything works.

seltix5 commented 1 year ago

They finally make it work on the server after changing the PHP handler from LSAPI to suPHP. I dont really understand what that trully means, I'm just reporting. Why connection_status() does not work with LSAPI? Is this expected or is something else on the handler settings?

(PHP 7.4.33 - SAPI cgi-fcgi)