1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

ext/sockets: adding Linux's TCP_USER_TIMEOUT constant.

Set TCP_USER_TIMEOUT to cap (ms) how long TCP will wait for ACKs on in-flight data
before aborting the connection; prevents stuck/half-open sessions and
enables faster failover vs default retransmission timeouts.

Co-authored-by: David Carlier <devnexen@gmail.com>

close GH-20708
This commit is contained in:
James Lucas
2025-12-15 21:23:44 +11:00
committed by David Carlier
parent b6f786a7ca
commit 46e55dd97c
7 changed files with 116 additions and 2 deletions

4
NEWS
View File

@@ -57,6 +57,10 @@ PHP NEWS
. Soap::__setCookie() when cookie name is a digit is now not stored and represented
as a string anymore but a int. (David Carlier)
- Sockets:
. Added the TCP_USER_TIMEOUT constant for Linux to set the maximum time in milliseconds
transmitted data can remain unacknowledged. (James Lucas)
- SPL:
. DirectoryIterator key can now work better with filesystem supporting larger
directory indexing. (David Carlier)

View File

@@ -94,6 +94,9 @@ PHP 8.6 UPGRADE NOTES
10. New Global Constants
========================================
- Sockets:
. TCP_USER_TIMEOUT (Linux only).
========================================
11. Changes to INI File Handling
========================================

View File

@@ -1773,7 +1773,7 @@ PHP_FUNCTION(socket_sendto)
RETURN_THROWS();
}
memset(&sll, 0, sizeof(sll));
memset(&sll, 0, sizeof(sll));
sll.sll_family = AF_PACKET;
sll.sll_ifindex = port;
@@ -2130,6 +2130,28 @@ PHP_FUNCTION(socket_set_option)
}
#endif
#if defined(TCP_USER_TIMEOUT)
case TCP_USER_TIMEOUT: {
zend_long timeout = zval_get_long(arg4);
// TCP_USER_TIMEOUT unsigned int
if (timeout < 0 || timeout > UINT_MAX) {
zend_argument_value_error(4, "must be of between 0 and %u", UINT_MAX);
RETURN_THROWS();
}
unsigned int val = (unsigned int)timeout;
optlen = sizeof(val);
opt_ptr = &val;
if (setsockopt(php_sock->bsd_socket, level, optname, opt_ptr, optlen) != 0) {
PHP_SOCKET_ERROR(php_sock, "Unable to set socket option", errno);
RETURN_FALSE;
}
RETURN_TRUE;
}
#endif
}
}

View File

@@ -602,6 +602,13 @@ const TCP_CONGESTION = UNKNOWN;
*/
const TCP_SYNCNT = UNKNOWN;
#endif
#ifdef TCP_USER_TIMEOUT
/**
* @var int
* @cvalue TCP_USER_TIMEOUT
*/
const TCP_USER_TIMEOUT = UNKNOWN;
#endif
#ifdef SO_ZEROCOPY
/**
* @var int

View File

@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: f754368e28f6e45bf3a63a403e49f5659c29d2c6 */
* Stub hash: 038081ca7bb98076d4b559d93b4c9300acc47160 */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_socket_select, 0, 4, MAY_BE_LONG|MAY_BE_FALSE)
ZEND_ARG_TYPE_INFO(1, read, IS_ARRAY, 1)
@@ -527,6 +527,9 @@ static void register_sockets_symbols(int module_number)
#if defined(TCP_SYNCNT)
REGISTER_LONG_CONSTANT("TCP_SYNCNT", TCP_SYNCNT, CONST_PERSISTENT);
#endif
#if defined(TCP_USER_TIMEOUT)
REGISTER_LONG_CONSTANT("TCP_USER_TIMEOUT", TCP_USER_TIMEOUT, CONST_PERSISTENT);
#endif
#if defined(SO_ZEROCOPY)
REGISTER_LONG_CONSTANT("SO_ZEROCOPY", SO_ZEROCOPY, CONST_PERSISTENT);
#endif

View File

@@ -0,0 +1,34 @@
--TEST--
Test if socket_set_option() works, option:TCP_USER_TIMEOUT
--EXTENSIONS--
sockets
--SKIPIF--
<?php
if (!defined('TCP_USER_TIMEOUT')) { die('skip TCP_USER_TIMEOUT is not defined'); }
if (PHP_INT_SIZE != 4) { die("skip 32-bit only"); }
?>
--FILE--
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) {
die('Unable to create AF_INET socket [socket]');
}
socket_set_block($socket);
try {
socket_setopt($socket, SOL_TCP, TCP_USER_TIMEOUT, -1);
} catch (\ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
$timeout = 200;
$retval_2 = socket_set_option($socket, SOL_TCP, TCP_USER_TIMEOUT, $timeout);
$retval_3 = socket_get_option($socket, SOL_TCP, TCP_USER_TIMEOUT);
var_dump($retval_2);
var_dump($retval_3 === $timeout);
socket_close($socket);
?>
--EXPECTF--
socket_setopt(): Argument #4 ($value) must be of between 0 and %d
bool(true)
bool(true)

View File

@@ -0,0 +1,41 @@
--TEST--
Test if socket_set_option() works, option:TCP_USER_TIMEOUT
--EXTENSIONS--
sockets
--SKIPIF--
<?php
if (!defined('TCP_USER_TIMEOUT')) { die('skip TCP_USER_TIMEOUT is not defined'); }
if (PHP_INT_SIZE != 8) { die("skip 64-bit only"); }
?>
--FILE--
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) {
die('Unable to create AF_INET socket [socket]');
}
socket_set_block($socket);
try {
socket_setopt($socket, SOL_TCP, TCP_USER_TIMEOUT, -1);
} catch (\ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
try {
socket_setopt($socket, SOL_TCP, TCP_USER_TIMEOUT, PHP_INT_MAX);
} catch (\ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
$timeout = 200;
$retval_2 = socket_set_option($socket, SOL_TCP, TCP_USER_TIMEOUT, $timeout);
$retval_3 = socket_get_option($socket, SOL_TCP, TCP_USER_TIMEOUT);
var_dump($retval_2);
var_dump($retval_3 === $timeout);
socket_close($socket);
?>
--EXPECTF--
socket_setopt(): Argument #4 ($value) must be of between 0 and %d
socket_setopt(): Argument #4 ($value) must be of between 0 and %d
bool(true)
bool(true)