Skip to content

Commit 46e55dd

Browse files
lucasnetaudevnexen
authored andcommitted
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 <[email protected]> close GH-20708
1 parent b6f786a commit 46e55dd

File tree

7 files changed

+116
-2
lines changed

7 files changed

+116
-2
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ PHP NEWS
5757
. Soap::__setCookie() when cookie name is a digit is now not stored and represented
5858
as a string anymore but a int. (David Carlier)
5959

60+
- Sockets:
61+
. Added the TCP_USER_TIMEOUT constant for Linux to set the maximum time in milliseconds
62+
transmitted data can remain unacknowledged. (James Lucas)
63+
6064
- SPL:
6165
. DirectoryIterator key can now work better with filesystem supporting larger
6266
directory indexing. (David Carlier)

UPGRADING

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ PHP 8.6 UPGRADE NOTES
9494
10. New Global Constants
9595
========================================
9696

97+
- Sockets:
98+
. TCP_USER_TIMEOUT (Linux only).
99+
97100
========================================
98101
11. Changes to INI File Handling
99102
========================================

ext/sockets/sockets.c

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1773,7 +1773,7 @@ PHP_FUNCTION(socket_sendto)
17731773
RETURN_THROWS();
17741774
}
17751775
1776-
memset(&sll, 0, sizeof(sll));
1776+
memset(&sll, 0, sizeof(sll));
17771777
sll.sll_family = AF_PACKET;
17781778
sll.sll_ifindex = port;
17791779
@@ -2130,6 +2130,28 @@ PHP_FUNCTION(socket_set_option)
21302130
}
21312131
#endif
21322132

2133+
#if defined(TCP_USER_TIMEOUT)
2134+
case TCP_USER_TIMEOUT: {
2135+
zend_long timeout = zval_get_long(arg4);
2136+
2137+
// TCP_USER_TIMEOUT unsigned int
2138+
if (timeout < 0 || timeout > UINT_MAX) {
2139+
zend_argument_value_error(4, "must be of between 0 and %u", UINT_MAX);
2140+
RETURN_THROWS();
2141+
}
2142+
2143+
unsigned int val = (unsigned int)timeout;
2144+
optlen = sizeof(val);
2145+
opt_ptr = &val;
2146+
if (setsockopt(php_sock->bsd_socket, level, optname, opt_ptr, optlen) != 0) {
2147+
PHP_SOCKET_ERROR(php_sock, "Unable to set socket option", errno);
2148+
RETURN_FALSE;
2149+
}
2150+
2151+
RETURN_TRUE;
2152+
}
2153+
#endif
2154+
21332155
}
21342156
}
21352157

ext/sockets/sockets.stub.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,13 @@
602602
*/
603603
const TCP_SYNCNT = UNKNOWN;
604604
#endif
605+
#ifdef TCP_USER_TIMEOUT
606+
/**
607+
* @var int
608+
* @cvalue TCP_USER_TIMEOUT
609+
*/
610+
const TCP_USER_TIMEOUT = UNKNOWN;
611+
#endif
605612
#ifdef SO_ZEROCOPY
606613
/**
607614
* @var int

ext/sockets/sockets_arginfo.h

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
--TEST--
2+
Test if socket_set_option() works, option:TCP_USER_TIMEOUT
3+
--EXTENSIONS--
4+
sockets
5+
--SKIPIF--
6+
<?php
7+
if (!defined('TCP_USER_TIMEOUT')) { die('skip TCP_USER_TIMEOUT is not defined'); }
8+
if (PHP_INT_SIZE != 4) { die("skip 32-bit only"); }
9+
?>
10+
--FILE--
11+
<?php
12+
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
13+
if (!$socket) {
14+
die('Unable to create AF_INET socket [socket]');
15+
}
16+
socket_set_block($socket);
17+
18+
try {
19+
socket_setopt($socket, SOL_TCP, TCP_USER_TIMEOUT, -1);
20+
} catch (\ValueError $e) {
21+
echo $e->getMessage(), PHP_EOL;
22+
}
23+
24+
$timeout = 200;
25+
$retval_2 = socket_set_option($socket, SOL_TCP, TCP_USER_TIMEOUT, $timeout);
26+
$retval_3 = socket_get_option($socket, SOL_TCP, TCP_USER_TIMEOUT);
27+
var_dump($retval_2);
28+
var_dump($retval_3 === $timeout);
29+
socket_close($socket);
30+
?>
31+
--EXPECTF--
32+
socket_setopt(): Argument #4 ($value) must be of between 0 and %d
33+
bool(true)
34+
bool(true)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
Test if socket_set_option() works, option:TCP_USER_TIMEOUT
3+
--EXTENSIONS--
4+
sockets
5+
--SKIPIF--
6+
<?php
7+
if (!defined('TCP_USER_TIMEOUT')) { die('skip TCP_USER_TIMEOUT is not defined'); }
8+
if (PHP_INT_SIZE != 8) { die("skip 64-bit only"); }
9+
?>
10+
--FILE--
11+
<?php
12+
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
13+
if (!$socket) {
14+
die('Unable to create AF_INET socket [socket]');
15+
}
16+
socket_set_block($socket);
17+
18+
try {
19+
socket_setopt($socket, SOL_TCP, TCP_USER_TIMEOUT, -1);
20+
} catch (\ValueError $e) {
21+
echo $e->getMessage(), PHP_EOL;
22+
}
23+
24+
try {
25+
socket_setopt($socket, SOL_TCP, TCP_USER_TIMEOUT, PHP_INT_MAX);
26+
} catch (\ValueError $e) {
27+
echo $e->getMessage(), PHP_EOL;
28+
}
29+
30+
$timeout = 200;
31+
$retval_2 = socket_set_option($socket, SOL_TCP, TCP_USER_TIMEOUT, $timeout);
32+
$retval_3 = socket_get_option($socket, SOL_TCP, TCP_USER_TIMEOUT);
33+
var_dump($retval_2);
34+
var_dump($retval_3 === $timeout);
35+
socket_close($socket);
36+
?>
37+
--EXPECTF--
38+
socket_setopt(): Argument #4 ($value) must be of between 0 and %d
39+
socket_setopt(): Argument #4 ($value) must be of between 0 and %d
40+
bool(true)
41+
bool(true)

0 commit comments

Comments
 (0)