Bug #71251 [Opn->Nab]: Blocking TCP socket connection does not timeout

From: Date: Sat, 07 May 2016 07:25:35 +0000
Subject: Bug #71251 [Opn->Nab]: Blocking TCP socket connection does not timeout
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-200933@lists.php.net to get a copy of this message
Edit report at https://bugs.php.net/bug.php?id=71251&edit=1

 ID:                 71251
 Updated by:         krakjoe@php.net
 Reported by:        gohanman at gmail dot com
 Summary:            Blocking TCP socket connection does not timeout
-Status:             Open
+Status:             Not a bug
 Type:               Bug
 Package:            Sockets related
 Operating System:   Windows 10
 PHP Version:        7.0.1
 Block user comment: N
 Private report:     N

 New Comment:

Forgot to change status ...


Previous Comments:
------------------------------------------------------------------------
[2016-05-07 07:23:53] krakjoe@php.net

There is no bug here.

The difference in behaviour you see is due to the way the machine at the other end deals with the
attempt to connect. "localhost" would hopefully drop the connection - actively refuse it,
while some other host (say google.com for example) will not.

Setting send/recv timeout doesn't work for connect - you are neither reading nor writing, but
awaiting readiness (for reading and writing) ...

Here's one way of performing a socket_connect() with a reliable timeout:

<?php

function socket_create_connect($domain, $type, $protocol, $host, $port, $timeout) {
	$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

	if (!$sock) {
		$errno = socket_last_error();

		throw new \RuntimeException(
			socket_strerror($errno), $errno);
	}

	socket_set_nonblock($sock);

	if (!socket_connect($sock, $host, $port)) {
		switch ($errno = socket_last_error()) {
			case SOCKET_EINPROGRESS:
				if (!socket_select($read, $write = [$sock], $except = [], $timeout, 0)) {
					throw new \RuntimeException(
						socket_strerror($errno), $errno);
				}
			break;

			default:
				throw new \RuntimeException(
					socket_strerror($errno), $errno);
		}
	}

	socket_set_block($sock);

	return $sock;
}

$sock = socket_create_connect(AF_INET, SOCK_STREAM, SOL_TCP, "google.com", 15674, 3);

socket_close($sock);

echo "Done";

------------------------------------------------------------------------
[2016-01-03 17:58:09] ab@php.net

Seems I'm not reproducing it with 7.0, neither with Apache nor with CLI on win10 build
10586.36. It could be something in your environment or ini, still need that info to start to debug.
Lets see whether you could dig down to it, or maybe someone else has more luck with reproducing.

Thanks.

------------------------------------------------------------------------
[2016-01-01 18:15:55] gohanman at gmail dot com

There's nothing on localhost:15674. The connection should be refused. When I run it (in Windows
10 via Apache 2.4, using mod_php) it does not print "Done". It blocks on the
socket_connect line seemingly forever - at least an hour.

That's the bug I'm trying to report. socket_connect to a non-existent end point with a
blocking socket is blocking indefinitely rather than timing out.

Using PHP5.6 (on the same Windows 10 machine via Apache 2.4, using mod_php) it *does* print
"Done" after about a second. So at least in this specific configuration the behavior seems
to have changed from 5.6 to 7.0.

------------------------------------------------------------------------
[2016-01-01 17:20:27] ab@php.net

Thanks for the report. I've a question - what is localhost:15674 supposed to have behind? On my
machine there's no such service, so I just see "Done" with your repro code - the
connection simply gets refused. If there's some difference in 7.0, it therefore should depend
and be reproducable on a concrete counterpart. Could you please provide more info on that?

Thanks.

------------------------------------------------------------------------
[2015-12-30 22:36:17] gohanman at gmail dot com

Description:
------------
I'm trying to set a connection timeout on a TCP port. I specifically want to verify whether a
remote service is alive. If the host does not respond within the specified timeout, I want to mark
it as down and continue with execution. If you happen to be running something on port 15674
substitute any closed port. I *want* the connection to fail in a predictable way.

In PHP 5.3 - 5.6, socket_connect blocks for roughly one second and then returns false. In PHP 7,
socket_connect blocks for infinity as far as I can tell. The longest I've let it run so far is
about 15 minutes but its clearly ignoring every built in execution limit. Restarting Apache is the
only thing that halts the script's execution.

I'm using php.ini-production with the following modules enabled: sockets, opcache, curl,
gettext, intl, mysqli, pdo_mysql, openssl, and xsl. Other than enabling modules I haven't made
any changes to the config.

Apache is 2.4.18 (Apache Lounge build). Both PHP and Apache are x86. The same code does work as
expected on the same machine in PHP 5.3 - PHP 5.6 so I don't think it's strictly a Windows
10 problem. I'm running the older PHP versions with different, older VC9 and VC11 Apache builds
of course.

Test script:
---------------
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($sock, SOL_SOCKET, SO_SNDTIMEO, array('sec' => 1, 'usec'
=> 0)); 
socket_set_block($sock);
$test = @socket_connect($sock,'localhost',15674);
socket_close($sock);
echo "Done";

Expected result:
----------------
$test gets boolean value true or false without more or less one second (with reasonable allowance
for whatever precision the underlying system can actually provide).

Actual result:
--------------
socket_connect blocks forever


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



--
Edit this bug report at https://bugs.php.net/bug.php?id=71251&edit=1


Thread (7 messages)

« previous php.bugs (#200933) next »