php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #20745 socket_accept() in blocking mode doesn't handle signal interrupts
Submitted: 2002-12-01 07:59 UTC Modified: 2008-03-06 15:25 UTC
Votes:18
Avg. Score:4.5 ± 0.7
Reproduced:17 of 17 (100.0%)
Same Version:2 (11.8%)
Same OS:14 (82.4%)
From: polone at townnews dot com Assigned:
Status: Closed Package: Sockets related
PHP Version: 4.2.3 OS: Linux
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: polone at townnews dot com
New email:
PHP Version: OS:

 

 [2002-12-01 07:59 UTC] polone at townnews dot com
The socket_accept() function doesn't appear to handle kernel level system interrupts. This is a problem because PHP scripts written to use pcntl_signal() won't be executed while socket_accept() is in blocking mode. THAT is a problem because the script can't be killed with:

kill -s SIGUSR1 [pid]

and can't reap zombie processes when SIGCHLD is given (not that is much of a problem on Linux, since you can use SIG_IGN).

A workaround I've made is to set a socket to non-blocking mode:

socket_set_nonblock($hSocket);

And then use a while loop like so:

while (($hClient = @socket_accept($hSocket)) === false) {
       
       // If this is a real error, we need to handle it. Unfortunately, in this
       // event we just die() essentially.
       
       if (!is_resource($hSocket)) {

           error_log(socket_strerror(socket_last_error($hSocket)));
           exit;

       }
       
       // We need to sleep for one second so that CPU isn't absorbed with this
       // process. This may seem clunky, but select() doesn't appear to work
       // correctly with accept() in PHP in blocking mode (that is EINTR does
       // not appear to work correctly), thus preventing signals from being
       // received. This, in effect, makes the process unkillable. This is the
       // workaround, for the moment.
       
       sleep(1);
       
}

While this works, it would be much better if it just returned when a system interrupt is given like the C-equivalent does. This works because the system can't receive messages from the kernel because it isn't blocking.

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2002-12-01 14:31 UTC] sniper@php.net
Please try using this CVS snapshot:

  http://snaps.php.net/php4-latest.tar.gz
 
For Windows:
 
  http://snaps.php.net/win32/php4-win32-latest.zip
 [2002-12-15 04:05 UTC] sniper@php.net
No feedback was provided. The bug is being suspended because
we assume that you are no longer experiencing the problem.
If this is not the case and you are able to provide the
information that was requested earlier, please do so and
change the status of the bug back to "Open". Thank you.


 [2003-01-02 02:50 UTC] piotr at t-p-l dot com
I'd like to revive this bug .. as of 4.3.0 this beheaviour persists .. I even tried this define(ticks=1); approach quoted in one of the related bugs .. it didnt help .. during a call to socket_accept() as far as I can see (I've tested only with SIGTERM and SIGINT) .. those handlers never get called .. with the new feature (the 3rd param on pcntl_signal() .. set to false) .. the socket_accept() call gets interrupted, but the handler is still not called. I would say there is a need to resolve this .. right now there is no way to cleanly kill a daemon process running php unless non blocking sockets are used .. which is basicaly a waste of cpu cycles .. 

Please respond as to the status of this problem asap.
 [2006-02-09 18:55 UTC] flachi at gmail dot com
I also have this problem with PHP 4.4.2. Is there any way to fix it ?
 [2006-05-25 19:31 UTC] lindsay at bitleap dot com
I still see this issue with php 5.1.4.  I did notice that the signal will queue up and on the next inbound connection the queued signal executes.
 [2007-03-12 18:54 UTC] antoine dot bajolet at tdf dot fr
Hello,

The issue still exists,

PHP Version => 4.4.6
System => Linux pentium2.antoine 2.6.20 #5 Sun Feb 18 16:28:17 CET 2007 i686
Build Date => Mar  6 2007 21:15:38

Complete code to reproduce error :

script.php
------------------------------------------------------------------

#!/usr/local/bin/php
<?php
define ( 'BIND_PORT', 3333 );
/* Signal Handler */
function signalHandler( $sig ) {
    switch ( $sig ) {
        case SIGTERM :
            exit( 0 );
            break;

        case SIGCHLD :
            pcntl_waitpid(-1,$status,WNOHANG);
            break;
    }
}
print "My PID is ".posix_getpid()."\n";
declare ( ticks = 1 );
pcntl_signal( SIGTERM, 'signalHandler' );
pcntl_signal( SIGCHLD, 'signalHandler' );

// infinite execution time
set_time_limit( 0 );
// Socket creation
$sock = socket_create ( AF_INET, SOCK_STREAM, SOL_TCP );
// Bind sur le port BIND_PORT
if ( !socket_bind( $sock, '0.0.0.0', BIND_PORT ) ) {
    print "Unable to bind to port " . BIND_PORT . " !\n";
    exit( 1 );
}
// Listening up to 16 buffets
socket_listen ( $sock, 16 );
// Infinite loop
while ( true ) {
    // Wait for incoming connections, hangs signal handling
    $subsock = socket_accept ( $sock );
    // Connection received, forking sub-process
    $subPid = pcntl_fork();

    if ( $subPid === 0 ) {
        // Get remote informations
        socket_getpeername( $subsock , $remoteAddr , $remotePort );
        socket_write( $subsock, 'Simple Socket Server accepting commands from ' . $remoteAddr . "\n" );
        // Whe are in interactive mode
        while ( true ) {
            $received = socket_read ( $subsock, 65536, PHP_NORMAL_READ );
            // Clean shutdown
            if ( !$received ) {
                socket_shutdown( $subsock, 2 );
                socket_close( $subsock );
                exit( 0 );
            }
            // Cleaning entry
            $received = trim( $received );
            if ( $received ) {
                // two commands : HELLO and QUIT
                switch ( $received ) {
                    case 'HELLO':
                        socket_write( $subsock, 'HELLO ' . $remoteAddr . "\n" );
                        break;
                    case 'QUIT':
                        // (shutdown)
                        socket_write( $subsock, 'BYE' . "\n" );
                        socket_shutdown( $subsock, 2 );
                        socket_close( $subsock );
                        exit( 0 );
                        break;
                    default :
                        socket_write( $subsock, 'Unknown command...' . "\n" );
                }
            }
        }
    }
} // while
?>

------------------------------------------------------------------

Commands :
[Shell1]$ ./script.php
My PID is 18135

[Shell2]$ kill 18135
<Happens nothing on Shell1...>

[Shell2]$  telnet localhost 3333
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host

[Shell1] : The script exits.

=> The signal is properly intercepted but the handler is only called when the script continues after socket_accept().

Whe are using a little more complex code when the fist process is a child of a fork itself : At this time, the only way to end it is a not very clean SIGKILL.

Regards,
AB
 [2007-11-29 01:51 UTC] steven dot gilberd at gmail dot com
This bug still appears to exist (PHP 5.2.3-1+b1 (cli)), on at least socket_accept().

The signals just queue up until the socket receives a connection and unblocks, at which point the signals are processed as normal.

I would like to request that this bug be reactivated; leaving it sitting here as 'No Feedback' isn't helping anyone.
 [2008-03-06 14:47 UTC] rose dot andrew at gmail dot com
This is not a bug.  socket_accept() will ignore all signals expect an alarm.  I have documented how to use this method of interrupting socket_accept() here: http://andrew-rose.blogspot.com/2008/02/php-getting-signals-through-to-blocking.html
 [2008-03-06 15:25 UTC] polone at townnews dot com
This IS a bug - all you've stated is _another_ workaround - less elegant than just setting the socket_accept() call to non-blocking and sleeping for 1 second (or you can sleep for microseconds in PHP 5) in the loop. It doesn't fix the underlying problem - which is that socket_accept() does not behave like accept().

If I issue SIGUSR1 - I should not have to set SIGALRM to 'reap' signals. Signals are suppose to interrupt blocking waits like this. I shoudn't have to make the accept() call non-blocking. 

At the very least - the non-standard behavior of this function (and it's caveats) should be documented.
 [2010-12-14 17:33 UTC] contact at aldrik dot net
Could this bug be reopened ? Mapping of posix API isn't well done, the workaround introduce latency and make people loose their time.

Signal are made to interrupt workflow, even on blocking situation.
 [2017-09-12 09:48 UTC] alien at rmail dot be
if socket_accept is blocking, but still handles SIGALRM, it can perfectly handle SIGCHLD.

basically, to workaround this, all child processes would need to send an ALRM when they are exiting, and the killing of the main process would also need to send an ALRM after the TERM signal.

Please fix this long-standing bug please, this will make php-daemons much more resource-efficient.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sun Dec 22 11:01:30 2024 UTC