php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #71251 Blocking TCP socket connection does not timeout
Submitted: 2015-12-30 22:36 UTC Modified: 2016-05-26 14:50 UTC
From: gohanman at gmail dot com Assigned:
Status: Not a bug Package: Sockets related
PHP Version: 7.0.1 OS: Windows 10
Private report: No CVE-ID: None
View Add Comment Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
You can add a comment by following this link or if you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: gohanman at gmail dot com
New email:
PHP Version: OS:

 

 [2015-12-30 22:36 UTC] 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

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-01-01 17:20 UTC] ab@php.net
-Status: Open +Status: Feedback
 [2016-01-01 17:20 UTC] 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.
 [2016-01-01 18:15 UTC] gohanman at gmail dot com
-Summary: Blocking TCP socket connection does not timeout +Summary: gohanman@gmail.com -Status: Feedback +Status: Open
 [2016-01-01 18:15 UTC] 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-02 00:57 UTC] requinix@php.net
-Summary: gohanman@gmail.com +Summary: Blocking TCP socket connection does not timeout
 [2016-01-03 17:58 UTC] 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-05-07 07:23 UTC] 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-05-07 07:25 UTC] krakjoe@php.net
-Status: Open +Status: Not a bug
 [2016-05-07 07:25 UTC] krakjoe@php.net
Forgot to change status ...
 [2016-05-26 14:50 UTC] gohanman at gmail dot com
I bumped into this again today and the behavior still seems slightly odd. Having recently run into a similar issue with PDO using mysqlnd I though that the ini setting for default_socket_timeout would apply here. It doesn't seem to.

As krakjoe noted, all of this only applies when the host you're connecting to is silently dropping packets to a closed port rather than actively rejecting them.

// This times out after a second
ini_set('default_socket_timeout', 1);
$fp = stream_socket_client('tcp://localhost:15674', $errno, $error);

// This does not
ini_set('default_socket_timeout', 1);
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_block($sock);
$test = socket_connect($sock,'localhost',15674);

The documentation does rightly note that default_socket_timeout applies to "socket based streams" but given the name of the setting it's at the very least unintuitive that it doesn't also apply to actual sockets.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Fri Mar 29 12:01:27 2024 UTC