Skip to content

Commit 2b5d32a

Browse files
authored
Use poll() instead of select()
* on *nix systems use poll() instead of select(); select has limitation on FD value to 1024 * test-utf8 is skipped if R does not support mbcs
1 parent 9ab368a commit 2b5d32a

7 files changed

Lines changed: 84 additions & 102 deletions

File tree

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
* fixes building under Solaris
44

5+
* replace `select()` with `poll()`
6+
57
# subprocess 0.8.2
68

79
* fixes in test cases for `testthat` 2.0

src/sub-linux.cc

Lines changed: 61 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <sys/time.h>
1414
#include <sys/types.h>
1515
#include <sys/wait.h>
16+
#include <sys/poll.h>
1617
#include <dlfcn.h>
1718

1819
#include <fcntl.h> /* Obtain O_* constant definitions */
@@ -31,7 +32,6 @@
3132
* this is redundant in non-Solaris builds but fixes Solaris */
3233
extern char ** environ;
3334

34-
3535
#ifdef TRUE
3636
#undef TRUE
3737
#endif
@@ -56,7 +56,7 @@ string strerror (int _code, const string & _message)
5656
message << _message << ": " << buffer.data();
5757
}
5858
else {
59-
message << _message << ": system error message could not be fetched";
59+
message << _message << ": system error message could not be fetched, errno = " << _code;
6060
}
6161

6262
return message.str();
@@ -96,7 +96,7 @@ static void exit_on_failure ()
9696
{
9797
void * process_handle = dlopen(NULL, RTLD_NOW);
9898
void * exit_handle = dlsym(process_handle, "exit");
99-
99+
100100
// it's hard to imagine a situation where this symbol would not be
101101
// present; regardless, we cause a SEGMENTATION error because the
102102
// child needs to die;
@@ -107,7 +107,7 @@ static void exit_on_failure ()
107107
*(int*)exit_handle = 0;
108108
++ret; // hide compiler warning
109109
}
110-
110+
111111
typedef void (* exit_t)(int);
112112
exit_t exit_fun = (exit_t)exit_handle;
113113
exit_fun(EXIT_FAILURE);
@@ -118,7 +118,7 @@ static void exit_on_failure ()
118118

119119

120120
/*
121-
* Duplicate handle and zero the original
121+
* Duplicate handle and zero the original
122122
*/
123123
inline void dup2 (int _from, int _to) {
124124
if (::dup2(_from, _to) < 0) {
@@ -215,6 +215,7 @@ void process_handle_t::spawn (const char * _command, char *const _arguments[],
215215
throw subprocess_exception(EALREADY, "process already started");
216216
}
217217

218+
int rc = 0;
218219
// can be addressed with PIPE_STDIN, PIPE_STDOUT, PIPE_STDERR
219220
pipe_holder pipes[3];
220221

@@ -247,7 +248,7 @@ void process_handle_t::spawn (const char * _command, char *const _arguments[],
247248
if (_termination_mode == TERMINATION_GROUP) {
248249
setsid();
249250
}
250-
251+
251252
/* if environment is empty, use parent's environment */
252253
if (!_environment) {
253254
_environment = environ;
@@ -284,6 +285,9 @@ void process_handle_t::spawn (const char * _command, char *const _arguments[],
284285
pipes[PIPE_STDIN][pipe_holder::WRITE] = HANDLE_CLOSED;
285286
pipes[PIPE_STDOUT][pipe_holder::READ] = HANDLE_CLOSED;
286287
pipes[PIPE_STDERR][pipe_holder::READ] = HANDLE_CLOSED;
288+
289+
// update process state
290+
wait(TIMEOUT_IMMEDIATE);
287291
}
288292

289293

@@ -346,75 +350,57 @@ struct enable_block_mode {
346350
enable_block_mode (int _fd) : fd (_fd) {
347351
set_block(fd);
348352
}
349-
353+
350354
~enable_block_mode () {
351355
set_non_block(fd);
352356
}
353357
};
354358

355359

356-
struct select_reader {
357-
358-
fd_set set;
359-
int max_fd;
360-
361-
select_reader () : max_fd(0) { }
362-
363-
void put_fd (int _fd) {
364-
FD_SET(_fd, &set);
365-
max_fd = std::max(max_fd, _fd);
366-
}
367-
368-
ssize_t timed_read (process_handle_t & _handle, pipe_type _pipe, int _timeout)
369-
{
370-
// this should never be called with "infinite" timeout
371-
if (_timeout < 0)
372-
return -1;
373-
374-
FD_ZERO(&set);
375-
if (_pipe & PIPE_STDOUT) {
376-
put_fd(_handle.pipe_stdout);
377-
_handle.stdout_.clear();
378-
}
379-
if (_pipe & PIPE_STDERR) {
380-
put_fd(_handle.pipe_stderr);
381-
_handle.stderr_.clear();
382-
}
383-
384-
struct timeval timeout;
385-
int start = clock_millisec(), timediff;
386-
ssize_t rc;
387-
388-
do {
389-
timediff = _timeout - (clock_millisec() - start);
390-
391-
// use max so that _timeout can be TIMEOUT_IMMEDIATE and yet
392-
// select can be tried at least once
393-
timeout.tv_sec = std::max(0, timediff/1000);
394-
timeout.tv_usec = std::max(0, (timediff % 1000) * 1000);
395-
396-
rc = select(max_fd + 1, &set, NULL, NULL, &timeout);
397-
if (rc == -1 && errno != EINTR && errno != EAGAIN)
398-
return -1;
399-
400-
} while(rc == 0 && timediff > 0);
401-
402-
// nothing to read; if errno == EINTR try reading one last time
403-
if (rc == 0 || errno == EAGAIN)
404-
return 0;
405-
406-
if (FD_ISSET(_handle.pipe_stdout, &set)) {
407-
rc = std::min(rc, (ssize_t)_handle.stdout_.read(_handle.pipe_stdout, mbcslocale));
408-
}
409-
if (FD_ISSET(_handle.pipe_stderr, &set)) {
410-
rc = std::min(rc, (ssize_t)_handle.stderr_.read(_handle.pipe_stderr, mbcslocale));
360+
ssize_t timed_read (process_handle_t & _handle, pipe_type _pipe, int _timeout)
361+
{
362+
struct pollfd fds[2] { { .fd = -1 }, { .fd = -1 } };
363+
364+
if (_pipe & PIPE_STDOUT) {
365+
fds[0].fd = _handle.pipe_stdout;
366+
fds[0].events = POLLIN;
367+
_handle.stdout_.clear();
368+
}
369+
370+
if (_pipe & PIPE_STDERR) {
371+
fds[1].fd = _handle.pipe_stderr;
372+
fds[1].events = POLLIN;
373+
_handle.stderr_.clear();
374+
}
375+
376+
time_t start = clock_millisec(), timediff = _timeout;
377+
ssize_t rc;
378+
379+
do {
380+
rc = poll(fds, 2, timediff);
381+
timediff = _timeout - (clock_millisec() - start);
382+
383+
// interrupted or kernel failed to allocate internal resources
384+
if (rc < 0 && (errno == EINTR || errno == EAGAIN)) {
385+
rc = 0;
411386
}
412-
413-
return rc;
387+
} while (rc == 0 && timediff > 0);
388+
389+
// nothing to read
390+
if (rc == 0) {
391+
return 0;
414392
}
415-
};
416393

394+
// TODO if an error occurs in the first read() it will be lost
395+
if (fds[0].fd != -1 && fds[0].revents == POLLIN) {
396+
rc = std::min(rc, (ssize_t)_handle.stdout_.read(_handle.pipe_stdout, mbcslocale));
397+
}
398+
if (fds[1].fd != -1 && fds[1].revents == POLLIN) {
399+
rc = std::min(rc, (ssize_t)_handle.stderr_.read(_handle.pipe_stderr, mbcslocale));
400+
}
417401

402+
return rc;
403+
}
418404

419405

420406
size_t process_handle_t::read (pipe_type _pipe, int _timeout)
@@ -423,25 +409,7 @@ size_t process_handle_t::read (pipe_type _pipe, int _timeout)
423409
throw subprocess_exception(ECHILD, "child does not exist");
424410
}
425411

426-
ssize_t rc;
427-
select_reader reader;
428-
429-
// infinite timeout
430-
if (_timeout == TIMEOUT_INFINITE) {
431-
enable_block_mode blocker_out(pipe_stdout);
432-
enable_block_mode blocker_err(pipe_stderr);
433-
rc = reader.timed_read(*this, _pipe, 1000);
434-
}
435-
// finite or no timeout
436-
else {
437-
rc = reader.timed_read(*this, _pipe, _timeout);
438-
439-
if (rc < 0 && errno == EAGAIN) {
440-
/* stdin pipe is opened with O_NONBLOCK, so this means "would block" */
441-
errno = 0;
442-
return 0;
443-
}
444-
}
412+
ssize_t rc = timed_read(*this, _pipe, _timeout);
445413

446414
if (rc < 0) {
447415
throw subprocess_exception(errno, "could not read from child process");
@@ -476,16 +444,17 @@ void process_handle_t::wait (int _timeout)
476444
return;
477445
}
478446

479-
/* to wait or not to wait? */
447+
/* to wait or not to wait? */
480448
int options = 0;
481-
if (_timeout >= 0)
449+
if (_timeout >= 0) {
482450
options = WNOHANG;
483-
451+
}
452+
484453
/* make the actual system call */
485454
int start = clock_millisec(), rc;
486455
do {
487456
rc = waitpid(child_id, &return_code, options);
488-
457+
489458
// there's been an error (<0)
490459
if (rc < 0) {
491460
throw subprocess_exception(errno, "waitpid() failed");
@@ -495,7 +464,9 @@ void process_handle_t::wait (int _timeout)
495464
} while (rc == 0 && _timeout > 0);
496465

497466
// the child is still running
498-
if (rc == 0) return;
467+
if (rc == 0) {
468+
return;
469+
}
499470

500471
// the child has exited or has been terminated
501472
if (WIFEXITED(return_code)) {
@@ -520,7 +491,6 @@ void process_handle_t::send_signal(int _signal)
520491
if (!child_id) {
521492
throw subprocess_exception(ECHILD, "child does not exist");
522493
}
523-
524494
int rc = ::kill(child_id, _signal);
525495

526496
if (rc < 0) {
@@ -580,7 +550,7 @@ int main (int argc, char ** argv)
580550
handle.spawn("/bin/bash", args, env, NULL, process_handle_t::TERMINATION_GROUP);
581551

582552
process_write(&handle, "echo A\n", 7);
583-
553+
584554
/* read is non-blocking so the child needs time to produce output */
585555
sleep(1);
586556
process_read(handle, PIPE_STDOUT, TIMEOUT_INFINITE);

subprocess.Rproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ Encoding: UTF-8
1212
RnwWeave: knitr
1313
LaTeX: pdfLaTeX
1414

15+
AutoAppendNewline: Yes
16+
StripTrailingWhitespace: Yes
17+
1518
BuildType: Package
1619
PackageUseDevtools: Yes
1720
PackageInstallArgs: --no-multiarch --with-keep.source --debug

tests/testthat/helper-processes.R

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ is_mac <- function ()
77
identical(tolower(Sys.info()[["sysname"]]), 'darwin')
88
}
99

10+
is_solaris <- function()
11+
{
12+
identical(tolower(Sys.info()[["sysname"]]), 'sunos')
13+
}
1014

1115
# --- R child ----------------------------------------------------------
1216

@@ -37,7 +41,7 @@ process_exists <- function (handle)
3741
return(length(grep(pid, output, fixed = TRUE)) > 0)
3842
}
3943
else {
40-
flag <- ifelse(is_mac(), "-p", "--pid")
44+
flag <- ifelse(is_mac() || is_solaris(), "-p", "--pid")
4145
rc <- system2("ps", c(flag, pid), stdout = NULL, stderr = NULL)
4246
return(rc == 0)
4347
}

tests/testthat/test-signals.R

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
11
context("signals")
22

33

4-
test_that("sending signal in Linux", {
5-
skip_if_not(is_linux())
4+
test_that("sending signal in Linux/MacOS/Solaris", {
5+
skip_if_not(is_linux() || is_mac() || is_solaris())
66

77
script_path <- file.path(getwd(), 'signal-trap.sh')
88
expect_true(file.exists(script_path))
9-
9+
1010
bash_path <- "/bin/bash"
1111
expect_true(file.exists(bash_path))
1212

1313
handle <- spawn_process(bash_path, c("-e", script_path))
1414
expect_true(process_exists(handle))
1515

16-
# excluded signals kill or stop the child
17-
for (signal in setdiff(signals, c(1, 9, 17, 19))) {
16+
# exclude signals to kill or stop the child
17+
skip <- c(SIGHUP, SIGKILL, SIGCHLD, SIGSTOP, if (is_solaris()) SIGQUIT)
18+
19+
for (signal in setdiff(signals, skip)) {
1820
process_send_signal(handle, signal)
1921
output <- process_read(handle, PIPE_STDOUT, TIMEOUT_INFINITE)
20-
2122
i <- which(signals == signal)
22-
expect_equal(output, names(signals)[[i]])
23+
expect_equal(output, names(signals)[[i]], info = names(signals)[[i]])
2324
}
2425
})
2526

@@ -53,3 +54,4 @@ test_that("sending signal in Windows", {
5354
expect_equal(process_wait(handle, TIMEOUT_INFINITE), 1)
5455
expect_false(process_exists(handle))
5556
})
57+

tests/testthat/test-termination.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ test_that("child process is terminated in Windows", {
5252
#
5353
# This test will, however, fail in plain R if termination_mode is
5454
# set to "child_only".
55-
test_that("child process is terminated in Linux", {
56-
skip_if_not(is_linux())
55+
test_that("child process is terminated in Linux/MacOS/SunOS", {
56+
skip_if_not(is_linux() || is_mac() || is_solaris())
5757

5858
# the parent shell script will start "sleep" and print its PID
5959
shell <- Sys.getenv("SHELL", '/bin/sh')

tests/testthat/test-utf8.R

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ test_that("C tests pass", {
66

77

88
test_that("multi-byte can come in parts", {
9-
skip_if_not(is_linux() || is_mac())
9+
skip_if_not(is_linux() || is_mac() || is_solaris())
10+
skip_if_not(l10n_info()$MBCS)
1011

1112
print_in_R <- function (handle, text) {
1213
process_write(handle, paste0("cat('", text, "')\n"))

0 commit comments

Comments
 (0)