Skip to content

Commit 69765d9

Browse files
committed
Fix network connect poll interuption handling
When connecting to socket, it is possible to get EINTR. In such case, there should be an another attempt to connect if we are not over the timeout. The timeout should be adjusted accordingly in that case. This fixes phpredis/phpredis#1881 Closes GH-16606
1 parent e1b4534 commit 69765d9

File tree

2 files changed

+74
-37
lines changed

2 files changed

+74
-37
lines changed

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ PHP NEWS
6363
. Fixed bug GH-16905 (Internal iterator functions can't handle UNDEF
6464
properties). (nielsdos)
6565

66+
- Streams:
67+
. Fixed network connect poll interuption handling. (Jakub Zelenka)
68+
6669
- Windows:
6770
. Fixed bug GH-16849 (Error dialog causes process to hang). (cmb)
6871

main/network.c

Lines changed: 71 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,35 @@ typedef int php_non_blocking_flags_t;
299299
fcntl(sock, F_SETFL, save)
300300
#endif
301301

302+
#if HAVE_GETTIMEOFDAY
303+
/* Subtract times */
304+
static inline void sub_times(struct timeval a, struct timeval b, struct timeval *result)
305+
{
306+
result->tv_usec = a.tv_usec - b.tv_usec;
307+
if (result->tv_usec < 0L) {
308+
a.tv_sec--;
309+
result->tv_usec += 1000000L;
310+
}
311+
result->tv_sec = a.tv_sec - b.tv_sec;
312+
if (result->tv_sec < 0L) {
313+
result->tv_sec++;
314+
result->tv_usec -= 1000000L;
315+
}
316+
}
317+
318+
static inline void php_network_set_limit_time(struct timeval *limit_time,
319+
struct timeval *timeout)
320+
{
321+
gettimeofday(limit_time, NULL);
322+
limit_time->tv_sec += timeout->tv_sec;
323+
limit_time->tv_usec += timeout->tv_usec;
324+
if (limit_time->tv_usec >= 1000000) {
325+
limit_time->tv_usec -= 1000000;
326+
limit_time->tv_sec++;
327+
}
328+
}
329+
#endif
330+
302331
/* Connect to a socket using an interruptible connect with optional timeout.
303332
* Optionally, the connect can be made asynchronously, which will implicitly
304333
* enable non-blocking mode on the socket.
@@ -351,25 +380,53 @@ PHPAPI int php_network_connect_socket(php_socket_t sockfd,
351380
* expected when a connection is actively refused. This way,
352381
* php_pollfd_for will return a mask with POLLOUT if the connection
353382
* is successful and with POLLPRI otherwise. */
354-
if ((n = php_pollfd_for(sockfd, POLLOUT|POLLPRI, timeout)) == 0) {
383+
int events = POLLOUT|POLLPRI;
355384
#else
356-
if ((n = php_pollfd_for(sockfd, PHP_POLLREADABLE|POLLOUT, timeout)) == 0) {
385+
int events = PHP_POLLREADABLE|POLLOUT;
386+
#endif
387+
struct timeval working_timeout;
388+
#if HAVE_GETTIMEOFDAY
389+
struct timeval limit_time, time_now;
390+
#endif
391+
if (timeout) {
392+
memcpy(&working_timeout, timeout, sizeof(working_timeout));
393+
#if HAVE_GETTIMEOFDAY
394+
php_network_set_limit_time(&limit_time, &working_timeout);
357395
#endif
358-
error = PHP_TIMEOUT_ERROR_VALUE;
359396
}
360397

361-
if (n > 0) {
362-
len = sizeof(error);
363-
/*
364-
BSD-derived systems set errno correctly
365-
Solaris returns -1 from getsockopt in case of error
366-
*/
367-
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (char*)&error, &len) != 0) {
398+
while (true) {
399+
n = php_pollfd_for(sockfd, events, timeout ? &working_timeout : NULL);
400+
if (n < 0) {
401+
if (errno == EINTR) {
402+
#if HAVE_GETTIMEOFDAY
403+
if (timeout) {
404+
gettimeofday(&time_now, NULL);
405+
406+
if (!timercmp(&time_now, &limit_time, <)) {
407+
/* time limit expired; no need for another poll */
408+
error = PHP_TIMEOUT_ERROR_VALUE;
409+
break;
410+
} else {
411+
/* work out remaining time */
412+
sub_times(limit_time, time_now, &working_timeout);
413+
}
414+
}
415+
#endif
416+
continue;
417+
}
368418
ret = -1;
419+
} else if (n == 0) {
420+
error = PHP_TIMEOUT_ERROR_VALUE;
421+
} else {
422+
len = sizeof(error);
423+
/* BSD-derived systems set errno correctly.
424+
* Solaris returns -1 from getsockopt in case of error. */
425+
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (char*)&error, &len) != 0) {
426+
ret = -1;
427+
}
369428
}
370-
} else {
371-
/* whoops: sockfd has disappeared */
372-
ret = -1;
429+
break;
373430
}
374431

375432
ok:
@@ -392,22 +449,6 @@ PHPAPI int php_network_connect_socket(php_socket_t sockfd,
392449
}
393450
/* }}} */
394451

395-
/* {{{ sub_times */
396-
static inline void sub_times(struct timeval a, struct timeval b, struct timeval *result)
397-
{
398-
result->tv_usec = a.tv_usec - b.tv_usec;
399-
if (result->tv_usec < 0L) {
400-
a.tv_sec--;
401-
result->tv_usec += 1000000L;
402-
}
403-
result->tv_sec = a.tv_sec - b.tv_sec;
404-
if (result->tv_sec < 0L) {
405-
result->tv_sec++;
406-
result->tv_usec -= 1000000L;
407-
}
408-
}
409-
/* }}} */
410-
411452
/* Bind to a local IP address.
412453
* Returns the bound socket, or -1 on failure.
413454
* */
@@ -777,7 +818,6 @@ PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock,
777818
}
778819
/* }}} */
779820

780-
781821
/* Connect to a remote host using an interruptible connect with optional timeout.
782822
* Optionally, the connect can be made asynchronously, which will implicitly
783823
* enable non-blocking mode on the socket.
@@ -809,13 +849,7 @@ php_socket_t php_network_connect_socket_to_host(const char *host, unsigned short
809849
if (timeout) {
810850
memcpy(&working_timeout, timeout, sizeof(working_timeout));
811851
#if HAVE_GETTIMEOFDAY
812-
gettimeofday(&limit_time, NULL);
813-
limit_time.tv_sec += working_timeout.tv_sec;
814-
limit_time.tv_usec += working_timeout.tv_usec;
815-
if (limit_time.tv_usec >= 1000000) {
816-
limit_time.tv_usec -= 1000000;
817-
limit_time.tv_sec++;
818-
}
852+
php_network_set_limit_time(&limit_time, &working_timeout);
819853
#endif
820854
}
821855

0 commit comments

Comments
 (0)