[Rd] R_SocketWaitMultiple(): Thinko leading to negative select() timeouts(?)
Henrik Bengtsson
henr|k@bengt@@on @end|ng |rom gm@||@com
Wed Mar 25 04:54:00 CET 2026
SUMMARY:
I think there is a thinko in R_SocketWaitMultiple() that can result in:
howmany = R_SelectEx(maxfd+1, &rfd, &wfd, NULL, &tv, NULL);
being called with a negative timeout 'tv' (tv.tv_sec and tv.tv_usec
negative). I think the bug is that:
if (mytimeout < 0 || R_wait_usec / 1e-6 < mytimeout - used)
should be:
if (mytimeout < 0 || R_wait_usec / 1e6 < mytimeout - used)
DETAILS:
Here is the code of interest:
int R_SocketWaitMultiple(int nsock, int *insockfd, int *ready, int *write,
double mytimeout)
{
fd_set rfd, wfd;
struct timeval tv;
double used = 0.0;
...
#ifdef Unix
if(R_wait_usec > 0) {
int delta;
if (mytimeout < 0 || R_wait_usec / 1e-6 < mytimeout - used)
delta = R_wait_usec;
else
delta = (int)ceil(1e6 * (mytimeout - used));
tv.tv_sec = delta / 1000000;
tv.tv_usec = (suseconds_t)(delta - tv.tv_sec * 1000000);
} else if (mytimeout >= 0) {
tv.tv_sec = (int)(mytimeout - used);
tv.tv_usec = (int)ceil(1e6 * (mytimeout - used - tv.tv_sec));
} else { /* always poll occasionally--not really necessary */
tv.tv_sec = 60;
tv.tv_usec = 0;
}
...
#endif
...
/* increment used value _before_ the select in case select
modifies tv (as Linux does) */
used += tv.tv_sec + 1e-6 * tv.tv_usec;
howmany = R_SelectEx(maxfd+1, &rfd, &wfd, NULL, &tv, NULL);
If we look carefully, we find the following condition:
R_wait_usec / 1e-6 < mytimeout - used
where:
extern int R_wait_usec;
double mytimeout;
double used;
and 'mytimeout' and 'used' are in seconds, whereas 'R_wait_usec' is in
microseconds. With 'R_wait_usec / 1e-6', we end up with a very large
number in units of micro-microseconds, i.e. picoseconds. I think that
is a thinko/typo, and the intended condition should be:
R_wait_usec / 1e6 < mytimeout - used
or
R_wait_usec * 1e-6 < mytimeout - used
That way both 'R_wait_usec / 1e6' and 'mytimeout - used' are in seconds.
The problem with the current implementation is this condition:
R_wait_usec / 1e-6 < mytimeout - used
will never be true, resulting in:
if (mytimeout < 0 || R_wait_usec / 1e-6 < mytimeout - used)
delta = R_wait_usec;
else
delta = (int)ceil(1e6 * (mytimeout - used));
always resolving to (when mytimeout >= 0):
delta = (int)ceil(1e6 * (mytimeout - used));
If 'used' > 'mytimeout', we end up with a negative 'delta', which in
turn results in a negative 'tv' structure from:
tv.tv_sec = delta / 1000000;
tv.tv_usec = (suseconds_t)(delta - tv.tv_sec * 1000000);
That causes 'used' to _decrease_ in:
used += tv.tv_sec + 1e-6 * tv.tv_usec;
and
howmany = R_SelectEx(maxfd+1, &rfd, &wfd, NULL, &tv, NULL);
being called with a negative timeout, which, I think, ends up calling
select() with a negative timeout.
/Henrik
PS. I found this one while troubleshooting a weird PSOCK cluster node
+ Sys.sleep(1.0) stall experienced by a user on WSL2.
More information about the R-devel
mailing list