Skip to content

stream_socket_accept() timeout sometimes doesn't work? #13220

Open
@l-aloha

Description

@l-aloha

Description

The following simple client-server socket code:

<?php // server.php

//	set_time_limit(0); // infinity for server, but as CLI the default is 0.

	define('DEFAULT_SERVER_THREAD_COUNT', 2);

	if (!function_exists('pcntl_fork')) die('PCNTL functions not available on this PHP installation');

	function logParent(string $message) : void
	{
		printf('[%s] %s%s', date('Ymd His'), $message, PHP_EOL);
	}

	function logChild(string $message) : void
	{
		printf('[%s][Pid: %u] %s%s', date('Ymd His'), getmypid(), $message, PHP_EOL);
	}

	abstract class Server
	{
		public function __construct(private string $address = DEFAULT_CONNECTION_ADDRESS) { }

		public function run(int $threadCount = DEFAULT_SERVER_THREAD_COUNT, int $acceptTimeOut = -1) : void
		{
			logParent('Server started');
			if (!($socket = stream_socket_server($this->address, $errno, $errstr)))
			{
				throw new Exception("$errstr ($errno)");
			}
			$pid = -1;
			try
			{
				for ($ii = $threadCount; $ii-- > 0; )
				{
					$pid = pcntl_fork();
					if (-1 == $pid)
					{
						die('Could not fork!');
					}
					else if ($pid) // parent
					{
						logParent("Created child: $pid");
						sleep(1); // delay between children creation
					}
					else // child
					{
						logChild('child started');
						break;
					}
				}

				if ($pid) // parent
				{
					for ($ii = $threadCount; $ii-- > 0; ) // parent wait (or manage) the children
					{	
						$_pid = pcntl_wait($status); // wait the children...
//						logParent("Child ended: $_pid");
					}
					sleep(1); // wait yet for the actual closure of the children
				}
				else // wait the client in child
				{
//					stream_set_timeout($socket, $acceptTimeOut); // unfortunately didn't help
					$cycle = true;
					while ($cycle)
					{
						logChild('waiting for client connection');
						if ($conn = @stream_socket_accept($socket, $acceptTimeOut))
						{
							logChild('client connected, processing...');
							$request = fread($conn, 4096);
							$response = $this->processRequest($request);
							fwrite($conn, $response);
							fclose($conn);
						}
						else
						{
							logChild('call idle function');
							$cycle = (-1 != $acceptTimeOut) && $this->idleFunction();
						}
					}
				}
			}
			finally
			{
				if ($pid) // parent
				{
//TODO maybe kill the (orphan) children if need?
					fclose($socket);
					logParent('Bye...');
				}
				else // child
				{
					logChild('child ended');
				}
			}
		}

		protected function idleFunction() : bool { return true; }
		abstract protected function processRequest(string $request) : string;
	}

	class ServerTest extends Server
	{
		protected function processRequest(string $request) : string { return 'OK'; }
	}

//	defined('DEFAULT_CONNECTION_ADDRESS') || define('DEFAULT_CONNECTION_ADDRESS', 'tcp://127.0.0.1:6666');
	define('SOCKET_FILE', '/tmp/test.sock');
	@unlink(SOCKET_FILE);
	defined('DEFAULT_CONNECTION_ADDRESS') || define('DEFAULT_CONNECTION_ADDRESS', 'unix://' . SOCKET_FILE);
	(new ServerTest(DEFAULT_CONNECTION_ADDRESS))->run(acceptTimeOut: 10);
?>

<?php // client.php
//	defined('DEFAULT_CONNECTION_ADDRESS') || define('DEFAULT_CONNECTION_ADDRESS', 'tcp://127.0.0.1:6666');
	define('SOCKET_FILE', '/tmp/test.sock');
	defined('DEFAULT_CONNECTION_ADDRESS') || define('DEFAULT_CONNECTION_ADDRESS', 'unix://' . SOCKET_FILE);

	$fp = stream_socket_client(DEFAULT_CONNECTION_ADDRESS, $errno, $errstr, 30);
	if (!$fp)
	{
		echo "$errstr ($errno)" . PHP_EOL;
	}
	else
	{
		fwrite($fp, 'KO');
		while (!feof($fp))
		{
			echo fgets($fp, 1024);
		}
		fclose($fp);
	}
?>

Resulted in this output:

$ php -d extension=pcntl server.php
[20240122 104636] Server started
[20240122 104636] Created child: 459146
[20240122 104636][Pid: 459146] child started
[20240122 104636][Pid: 459146] waiting for client connection
[20240122 104637] Created child: 459147
[20240122 104637][Pid: 459147] child started
[20240122 104637][Pid: 459147] waiting for client connection
[20240122 104643][Pid: 459147] client connected, processing...
[20240122 104643][Pid: 459147] waiting for client connection
[20240122 104646][Pid: 459146] call idle function
[20240122 104646][Pid: 459146] waiting for client connection
[20240122 104653][Pid: 459147] call idle function
[20240122 104653][Pid: 459147] waiting for client connection
[20240122 104656][Pid: 459146] call idle function
[20240122 104656][Pid: 459146] waiting for client connection
[20240122 104703][Pid: 459147] call idle function
[20240122 104703][Pid: 459147] waiting for client connection
[20240122 104706][Pid: 459146] call idle function
[20240122 104706][Pid: 459146] waiting for client connection
[20240122 104707][Pid: 459146] client connected, processing...
[20240122 104707][Pid: 459146] waiting for client connection
[20240122 104708][Pid: 459147] client connected, processing...
[20240122 104708][Pid: 459147] waiting for client connection
[20240122 104708][Pid: 459146] client connected, processing...
[20240122 104708][Pid: 459146] waiting for client connection
[20240122 104709][Pid: 459147] client connected, processing...
[20240122 104709][Pid: 459147] waiting for client connection
[20240122 104710][Pid: 459146] client connected, processing...
[20240122 104710][Pid: 459146] waiting for client connection
[20240122 104720][Pid: 459146] call idle function
[20240122 104720][Pid: 459146] waiting for client connection
[20240122 104730][Pid: 459146] call idle function
[20240122 104730][Pid: 459146] waiting for client connection
[20240122 104740][Pid: 459146] call idle function
[20240122 104740][Pid: 459146] waiting for client connection
[20240122 104741][Pid: 459147] client connected, processing...
[20240122 104741][Pid: 459147] waiting for client connection
[20240122 104751][Pid: 459147] call idle function
[20240122 104751][Pid: 459147] waiting for client connection
[20240122 104801][Pid: 459147] call idle function
[20240122 104801][Pid: 459147] waiting for client connection
[20240122 104811][Pid: 459147] call idle function
[20240122 104811][Pid: 459147] waiting for client connection
	...
[20240122 105211][Pid: 459147] call idle function
[20240122 105211][Pid: 459147] waiting for client connection

But I expected this output instead:

$ php -d extension=pcntl server.php
[20240122 104636] Server started
[20240122 104636] Created child: 459146
[20240122 104636][Pid: 459146] child started
[20240122 104636][Pid: 459146] waiting for client connection
[20240122 104637] Created child: 459147
[20240122 104637][Pid: 459147] child started
[20240122 104637][Pid: 459147] waiting for client connection
[20240122 104643][Pid: 459147] client connected, processing...
[20240122 104643][Pid: 459147] waiting for client connection
[20240122 104646][Pid: 459146] call idle function
[20240122 104646][Pid: 459146] waiting for client connection
[20240122 104653][Pid: 459147] call idle function
[20240122 104653][Pid: 459147] waiting for client connection
[20240122 104656][Pid: 459146] call idle function
[20240122 104656][Pid: 459146] waiting for client connection
[20240122 104703][Pid: 459147] call idle function
[20240122 104703][Pid: 459147] waiting for client connection
[20240122 104706][Pid: 459146] call idle function
[20240122 104706][Pid: 459146] waiting for client connection
[20240122 104707][Pid: 459146] client connected, processing...
[20240122 104707][Pid: 459146] waiting for client connection
[20240122 104708][Pid: 459147] client connected, processing...
[20240122 104708][Pid: 459147] waiting for client connection
[20240122 104708][Pid: 459146] client connected, processing...
[20240122 104708][Pid: 459146] waiting for client connection
[20240122 104709][Pid: 459147] client connected, processing...
[20240122 104709][Pid: 459147] waiting for client connection
[20240122 104710][Pid: 459146] client connected, processing...
[20240122 104710][Pid: 459146] waiting for client connection
[20240122 104719][Pid: 459147] call idle function
[20240122 104719][Pid: 459147] waiting for client connection
[20240122 104720][Pid: 459146] call idle function
[20240122 104720][Pid: 459146] waiting for client connection
[20240122 104729][Pid: 459147] call idle function
[20240122 104729][Pid: 459147] waiting for client connection
[20240122 104730][Pid: 459146] call idle function
[20240122 104730][Pid: 459146] waiting for client connection
	...

The problem:

Server forked 2 child processes (459146, 459147), that wait for client connections (stream_socket_accept) with 10 sec timeout.
If a client connected to one of the processes, then that will process the request, and wait a client connection again with 10 sec timeout.
If not client connected within 10 sec (timeout), then run idle function and wait to a client connection again with 10 sec timeout.
Unfortunately, after a few client request connection, if again wait to a client connection, then only one process (459146) wait with timeout, the another process (459147) wait infinity. If connect to this other process (459147), this thing will be swapped...

Is the problem in PHP? Or with OS? Or with VPS?

PHP Version

PHP 8.3+

Operating System

Rocky Linux 9.3 (6.6.10-1.el9)

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions