everything is ok, when pipes are non blocking on parent's side and blocking on client's side, and select is not used (not necessary for non blocking IO)

master
Marc Wäckerlin 20 years ago
parent 1386c24c79
commit 7c07d63570
  1. 126
      mrw/exec.cpp

@ -9,6 +9,9 @@
@license LGPL, see file <a href="license.html">COPYING</a>
$Log$
Revision 1.8 2004/12/18 21:00:09 marc
everything is ok, when pipes are non blocking on parent's side and blocking on client's side, and select is not used (not necessary for non blocking IO)
Revision 1.7 2004/12/17 17:38:57 marc
it works perfectly with blocking pipes and buffer size of 1
@ -36,12 +39,10 @@
#include <mrw/unistd.hpp>
#include <mrw/exception.hpp>
#include <mrw/stdext.hpp> // max
#include <sys/wait.h> // waitpid
#include <unistd.h> // fork, exec
#include <string.h> // memcpy
#include <sys/wait.h> // waitpid#include <unistd.h> // fork, exec
#include <string.h> // memcpy
#include <assert.h> // assert
#include <iostream>
mrw::ExecutionFailedExc::ExecutionFailedExc(const std::string& w,
const std::string& c)
throw(std::bad_exception):
@ -133,7 +134,7 @@ mrw::Exec& mrw::Exec::execute(bool exc) throw(std::exception) {
program. */
_success = false;
_res = _err = "";
mrw::Pipe stdOut(true), stdErr(true);
mrw::Pipe stdOut(mrw::Pipe::block_output), stdErr(mrw::Pipe::block_output);
if (!stdOut || !stdErr)
throw mrw::ExecutionFailedExc("cannot create pipe", *_cmd);
pid_t pid(fork());
@ -144,46 +145,34 @@ mrw::Exec& mrw::Exec::execute(bool exc) throw(std::exception) {
stdErr.close_out();
if (!stdOut || !stdErr)
throw ExecutionFailedExc("cannot close pipe", *_cmd);
ssize_t num1(-1), num2(-1);
fd_set readfds;
char buf[1]; // used to have larger buffer, but since non blocking fails...
ssize_t num1(1), num2(1);
char buf[4096];
int res(0), s(0);
/** @bug It sometimes did not get the whole input since I
changed to non blocking pipes. Now pipes are blocking
again and buffer size is 1, so I hope all problems are
resolved. Please report any problems! */
while (num1||num2) try { // not all end of file
/** @bug @b Solved: It sometimes did not get the whole input
since I changed to non blocking pipes. Now I have found
the solution to the problems: Non blocking IO does not
work in conjunction with @c select! Also the pipe must
not be nonblocking on both ends, but only on one. */
while (num1||num2||!res) try { // not end of files or child terminated
if (!res && (res=waitpid(pid, &s, WNOHANG)))
if (res!=pid || WIFEXITED(s)!=0 && WEXITSTATUS(s)!=0)
throw ExecutionFailedExc("execution failed", *_cmd);
// select...
FD_ZERO(&readfds);
if (num1) FD_SET(stdOut.istream(), &readfds);
if (num2) FD_SET(stdErr.istream(), &readfds);
int n(mrw::max((num1?stdOut.istream():0),
(num2?stdErr.istream():0)));
timeval tm = {0, 10000};
int num = select(n+1, &readfds, 0, 0, 0);
if (num<0) throw ExecutionFailedExc("select failed", *_cmd);
if (num==0 && res==pid) break; // process ended, no data left
if (num1<=0&&num2<=0) usleep(10000); // nothing to read last time
// check and handle stdout
if (num>0 && FD_ISSET(stdOut.istream(), &readfds))
if ((num1=read(stdOut.istream(), buf, sizeof(buf)))>0)
_res += std::string(buf, num1);
else if (num1==-1 && (errno!=EINTR&&errno!=EAGAIN))
throw ExecutionFailedExc("readin stdout", *_cmd);
if (num1 && (num1=read(stdOut.istream(), buf, sizeof(buf)))>0)
_res += std::string(buf, num1);
else if (num1==-1 && (errno!=EINTR&&errno!=EAGAIN))
throw ExecutionFailedExc("readin stdout", *_cmd);
// check and handle stderr
if (num>0 && FD_ISSET(stdErr.istream(), &readfds))
if ((num2=read(stdErr.istream(), buf, sizeof(buf)))>0)
_err += std::string(buf, num2);
else if (num2==-1 && (errno!=EINTR&&errno!=EAGAIN))
throw ExecutionFailedExc("readin stderr", *_cmd);
if (num2 && (num2=read(stdErr.istream(), buf, sizeof(buf)))>0)
_err += std::string(buf, num2);
else if (num2==-1 && (errno!=EINTR&&errno!=EAGAIN))
throw ExecutionFailedExc("readin stderr", *_cmd);
} catch (...) {
if (exc) throw;
_success = false;
return *this;
}
std::cout<<"num1="<<num1<<", num2="<<num2<<std::endl;
} else { // child
stdOut.close_in();
stdErr.close_in();
@ -209,8 +198,8 @@ mrw::Exec& mrw::Exec::execute(const std::string& input, bool exc)
the program. */
_success = false;
_res = _err = "";
mrw::Pipe stdIn(true), // child terminates if stdin is non-blocking
stdOut(true), stdErr(true); // non blocking is also a problem here
mrw::Pipe stdIn(mrw::Pipe::block_input),
stdOut(mrw::Pipe::block_output), stdErr(mrw::Pipe::block_output);
if (!stdIn || !stdOut || !stdErr)
throw mrw::ExecutionFailedExc("cannot create pipe", *_cmd);
pid_t pid(fork());
@ -221,60 +210,43 @@ mrw::Exec& mrw::Exec::execute(const std::string& input, bool exc)
stdErr.close_out();
if (!stdIn || !stdOut || !stdErr)
throw ExecutionFailedExc("cannot close pipes", *_cmd);
ssize_t num0(-1), num1(-1), num2(-1);
ssize_t num0(1), num1(1), num2(1);
std::string in(input);
fd_set writefds, readfds;
char buf[1]; // used to have larger buffer, but since non blocking fails...
char buf[4096];
int res(0), s(0);
/** @bug It sometimes did not get the whole input since I
changed to non blocking pipes. Now pipes are blocking
again and buffer size is 1, so I hope all problems are
resolved. Please report any problems! */
while (num0||num1||num2) try { // not all end of file
/** @bug @b Solved: It sometimes did not get the whole input
since I changed to non blocking pipes. Now I have found
the solution to the problems: Non blocking IO does not
work in conjunction with @c select! Also the pipe must
not be nonblocking on both ends, but only on one. */
while (num0||num1||num2||!res) try { // not end of files or child terminated
if (!res && (res=waitpid(pid, &s, WNOHANG)))
if (res!=pid || WIFEXITED(s)!=0 && WEXITSTATUS(s)!=0)
throw ExecutionFailedExc("execution failed", *_cmd);
// select...
FD_ZERO(&writefds);
FD_ZERO(&readfds);
if (num0) FD_SET(stdIn.ostream(), &writefds);
if (num1) FD_SET(stdOut.istream(), &readfds);
if (num2) FD_SET(stdErr.istream(), &readfds);
int n(mrw::max((num0?stdIn.ostream():0),
mrw::max((num1?stdOut.istream():0),
(num2?stdErr.istream():0))));
timeval tm = {0, 10000};
int num = select(n+1, &readfds, &writefds, 0, &tm);
if (num<0) throw ExecutionFailedExc("select failed", *_cmd);
if (num==0 && res==pid) break; // process ended, no data left
if (num0<=0&&num1<=0&&num2<=0) usleep(10000); // no activity last time
// check and handle stdin
if (num0 && num>0
&& FD_ISSET(stdIn.ostream(), &writefds))
if ((num0=write(stdIn.ostream(), in.c_str(), in.size()))>0) {
if ((unsigned int)num0<in.size())
in = in.substr(num0);
else if ((unsigned int)num0==in.size())
num0=0, stdIn.close_out();
} else if (num0==-1 && (errno!=EINTR&&errno!=EAGAIN))
throw ExecutionFailedExc("writing stdin", *_cmd);
if (num0 && (num0=write(stdIn.ostream(), in.c_str(), in.size()))>0) {
if ((unsigned int)num0<in.size())
in = in.substr(num0);
else if ((unsigned int)num0==in.size())
num0=0, stdIn.close_out();
} else if (num0==-1 && (errno!=EINTR&&errno!=EAGAIN))
throw ExecutionFailedExc("writing stdin", *_cmd);
// check and handle stdout
if (num>0 && FD_ISSET(stdOut.istream(), &readfds))
if ((num1=read(stdOut.istream(), buf, sizeof(buf)))>0)
_res += std::string(buf, num1);
else if (num1==-1 && (errno!=EINTR&&errno!=EAGAIN))
throw ExecutionFailedExc("readin stdout", *_cmd);
if (num1 && (num1=read(stdOut.istream(), buf, sizeof(buf)))>0)
_res += std::string(buf, num1);
else if (num1==-1 && (errno!=EINTR&&errno!=EAGAIN))
throw ExecutionFailedExc("readin stdout", *_cmd);
// check and handle stderr
if (num>0 && FD_ISSET(stdErr.istream(), &readfds))
if ((num2=read(stdErr.istream(), buf, sizeof(buf)))>0)
_err += std::string(buf, num2);
else if (num2==-1 && (errno!=EINTR&&errno!=EAGAIN))
throw ExecutionFailedExc("readin stderr", *_cmd);
if (num2 && (num2=read(stdErr.istream(), buf, sizeof(buf)))>0)
_err += std::string(buf, num2);
else if (num2==-1 && (errno!=EINTR&&errno!=EAGAIN))
throw ExecutionFailedExc("readin stderr", *_cmd);
} catch (...) {
if (exc) throw;
_success = false;
return *this;
}
std::cout<<"num0="<<num0<<", num1="<<num1<<", num2="<<num2<<std::endl;
} else { // child
stdIn.close_out();
stdOut.close_in();

Loading…
Cancel
Save