Commit ffead26e authored by Zhangyi Chen's avatar Zhangyi Chen

Default implementation of read_command_output is based on popen right now since

we met some weird bug
parent 6c34eb53
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
// Author: Zhangyi Chen (chenzhangyi01@baidu.com) // Author: Zhangyi Chen (chenzhangyi01@baidu.com)
// Date: 2017/11/04 17:37:43 // Date: 2017/11/04 17:37:43
#include <gflags/gflags.h>
#include "butil/build_config.h" #include "butil/build_config.h"
#include "butil/logging.h" #include "butil/logging.h"
...@@ -31,7 +32,7 @@ uint64_t BAIDU_WEAK bthread_usleep(uint64_t microseconds); ...@@ -31,7 +32,7 @@ uint64_t BAIDU_WEAK bthread_usleep(uint64_t microseconds);
namespace butil { namespace butil {
const int CHILD_STACK_SIZE = 64 * 1024; const int CHILD_STACK_SIZE = 256 * 1024;
struct ChildArgs { struct ChildArgs {
const char* cmd; const char* cmd;
...@@ -48,7 +49,7 @@ int launch_child_process(void* args) { ...@@ -48,7 +49,7 @@ int launch_child_process(void* args) {
_exit(1); _exit(1);
} }
int read_command_output(std::ostream& os, const char* cmd) { int read_command_output_through_clone(std::ostream& os, const char* cmd) {
int pipe_fd[2]; int pipe_fd[2];
if (pipe(pipe_fd) != 0) { if (pipe(pipe_fd) != 0) {
PLOG(ERROR) << "Fail to pipe"; PLOG(ERROR) << "Fail to pipe";
...@@ -71,7 +72,7 @@ int read_command_output(std::ostream& os, const char* cmd) { ...@@ -71,7 +72,7 @@ int read_command_output(std::ostream& os, const char* cmd) {
child_stack = child_stack_mem + CHILD_STACK_SIZE; child_stack = child_stack_mem + CHILD_STACK_SIZE;
// ^ Assume stack grows downward // ^ Assume stack grows downward
cpid = clone(launch_child_process, child_stack, cpid = clone(launch_child_process, child_stack,
__WCLONE | CLONE_VM | SIGCHLD, &args); __WCLONE | CLONE_VM | SIGCHLD | CLONE_UNTRACED, &args);
if (cpid < 0) { if (cpid < 0) {
PLOG(ERROR) << "Fail to clone child process"; PLOG(ERROR) << "Fail to clone child process";
rc = -1; rc = -1;
...@@ -97,7 +98,7 @@ int read_command_output(std::ostream& os, const char* cmd) { ...@@ -97,7 +98,7 @@ int read_command_output(std::ostream& os, const char* cmd) {
pipe_fd[0] = -1; pipe_fd[0] = -1;
for (;;) { for (;;) {
pid_t wpid = waitpid(cpid, &wstatus, WNOHANG); pid_t wpid = waitpid(cpid, &wstatus, WNOHANG | __WALL);
if (wpid > 0) { if (wpid > 0) {
break; break;
} }
...@@ -141,15 +142,15 @@ END: ...@@ -141,15 +142,15 @@ END:
return rc; return rc;
} }
} // namespace butil DEFINE_bool(run_command_through_clone, false,
"(Linux specific) Run command with clone syscall to "
"avoid the costly page table duplication");
#else // OS_LINUX #endif // OS_LINUX
#include <stdio.h> #include <stdio.h>
namespace butil { int read_command_output_through_popen(std::ostream& os, const char* cmd) {
int read_command_output(std::ostream& os, const char* cmd) {
FILE* pipe = popen(cmd, "r"); FILE* pipe = popen(cmd, "r");
if (pipe == NULL) { if (pipe == NULL) {
return -1; return -1;
...@@ -170,9 +171,31 @@ int read_command_output(std::ostream& os, const char* cmd) { ...@@ -170,9 +171,31 @@ int read_command_output(std::ostream& os, const char* cmd) {
// retry; // retry;
} }
} }
return pclose(pipe);
const int wstatus = pclose(pipe);
if (wstatus < 0) {
return wstatus;
}
if (WIFEXITED(wstatus)) {
return WEXITSTATUS(wstatus);
}
if (WIFSIGNALED(wstatus)) {
os << "Child process was killed by signal "
<< WTERMSIG(wstatus);
}
errno = ECHILD;
return -1;
} }
} // namespace butil int read_command_output(std::ostream& os, const char* cmd) {
#if !defined(OS_LINUX)
return read_command_output_through_popen(os, cmd);
#else
return FLAGS_run_command_through_clone
? read_command_output_through_clone(os, cmd)
: read_command_output_through_popen(os, cmd);
#endif
}
#endif // OS_LINUX } // namespace butil
...@@ -6,40 +6,121 @@ ...@@ -6,40 +6,121 @@
#include "butil/popen.h" #include "butil/popen.h"
#include "butil/errno.h" #include "butil/errno.h"
#include "butil/strings/string_piece.h" #include "butil/strings/string_piece.h"
#include "butil/build_config.h"
#include <gtest/gtest.h> #include <gtest/gtest.h>
namespace butil {
extern int read_command_output_through_clone(std::ostream&, const char*);
extern int read_command_output_through_popen(std::ostream&, const char*);
}
namespace { namespace {
class PopenTest : public testing::Test { class PopenTest : public testing::Test {
}; };
TEST(PopenTest, sanity) { TEST(PopenTest, posix_popen) {
std::ostringstream oss;
int rc = butil::read_command_output_through_popen(oss, "echo \"Hello World\"");
ASSERT_EQ(0, rc) << berror(errno);
ASSERT_EQ("Hello World\n", oss.str());
oss.str("");
rc = butil::read_command_output_through_popen(oss, "exit 1");
EXPECT_EQ(1, rc) << berror(errno);
ASSERT_TRUE(oss.str().empty()) << oss;
oss.str("");
rc = butil::read_command_output_through_popen(oss, "kill -9 $$");
ASSERT_EQ(-1, rc);
ASSERT_EQ(errno, ECHILD);
ASSERT_TRUE(butil::StringPiece(oss.str()).ends_with("was killed by signal 9"));
oss.str("");
rc = butil::read_command_output_through_clone(oss, "kill -15 $$");
ASSERT_EQ(-1, rc);
ASSERT_EQ(errno, ECHILD);
ASSERT_TRUE(butil::StringPiece(oss.str()).ends_with("was killed by signal 15"));
oss.str("");
ASSERT_EQ(0, butil::read_command_output_through_clone(oss, "for i in `seq 1 100000`; do echo -n '=' ; done"));
ASSERT_EQ(100000u, oss.str().length());
std::string expected;
expected.resize(100000, '=');
ASSERT_EQ(expected, oss.str());
}
#if defined(OS_LINUX)
TEST(PopenTest, clone) {
std::ostringstream oss; std::ostringstream oss;
int rc = butil::read_command_output(oss, "echo \"Hello World\""); int rc = butil::read_command_output_through_clone(oss, "echo \"Hello World\"");
ASSERT_EQ(0, rc) << berror(errno); ASSERT_EQ(0, rc) << berror(errno);
ASSERT_EQ("Hello World\n", oss.str()); ASSERT_EQ("Hello World\n", oss.str());
oss.str(""); oss.str("");
rc = butil::read_command_output(oss, "exit 1"); rc = butil::read_command_output_through_clone(oss, "exit 1");
ASSERT_EQ(1, rc) << berror(errno); ASSERT_EQ(1, rc) << berror(errno);
ASSERT_TRUE(oss.str().empty()) << oss.str(); ASSERT_TRUE(oss.str().empty()) << oss;
oss.str(""); oss.str("");
rc = butil::read_command_output(oss, "kill -9 $$"); rc = butil::read_command_output_through_clone(oss, "kill -9 $$");
ASSERT_EQ(-1, rc); ASSERT_EQ(-1, rc);
ASSERT_EQ(errno, ECHILD); ASSERT_EQ(errno, ECHILD);
ASSERT_TRUE(butil::StringPiece(oss.str()).ends_with("was killed by signal 9")); ASSERT_TRUE(butil::StringPiece(oss.str()).ends_with("was killed by signal 9"));
oss.str(""); oss.str("");
rc = butil::read_command_output(oss, "kill -15 $$"); rc = butil::read_command_output_through_clone(oss, "kill -15 $$");
ASSERT_EQ(-1, rc); ASSERT_EQ(-1, rc);
ASSERT_EQ(errno, ECHILD); ASSERT_EQ(errno, ECHILD);
ASSERT_TRUE(butil::StringPiece(oss.str()).ends_with("was killed by signal 15")); ASSERT_TRUE(butil::StringPiece(oss.str()).ends_with("was killed by signal 15"));
oss.str(""); oss.str("");
ASSERT_EQ(0, butil::read_command_output(oss, "for i in `seq 1 100000`; do echo -n '=' ; done")); ASSERT_EQ(0, butil::read_command_output_through_clone(oss, "for i in `seq 1 100000`; do echo -n '=' ; done"));
ASSERT_EQ(100000u, oss.str().length()) << oss.str(); ASSERT_EQ(100000u, oss.str().length());
std::string expected; std::string expected;
expected.resize(100000, '='); expected.resize(100000, '=');
ASSERT_EQ(expected, oss.str()); ASSERT_EQ(expected, oss.str());
} }
struct CounterArg {
volatile int64_t counter;
volatile bool stop;
};
static void* counter_thread(void* args) {
CounterArg* ca = (CounterArg*)args;
while (!ca->stop) {
++ca->counter;
}
return NULL;
}
static int fork_thread(void* arg) {
usleep(100 * 1000);
_exit(0);
}
const int CHILD_STACK_SIZE = 64 * 1024;
TEST(PopenTest, does_vfork_suspend_all_threads) {
pthread_t tid;
CounterArg ca = { 0 , false };
ASSERT_EQ(0, pthread_create(&tid, NULL, counter_thread, &ca));
usleep(100 * 1000);
char* child_stack_mem = (char*)malloc(CHILD_STACK_SIZE);
void* child_stack = child_stack_mem + CHILD_STACK_SIZE;
const int64_t counter_before_fork = ca.counter;
pid_t cpid = clone(fork_thread, child_stack, CLONE_VFORK, NULL);
const int64_t counter_after_fork = ca.counter;
usleep(100 * 1000);
const int64_t counter_after_sleep = ca.counter;
int ws;
ca.stop = true;
pthread_join(tid, NULL);
std::cout << "bc=" << counter_before_fork << " ac=" << counter_after_fork
<< " as=" << counter_after_sleep
<< std::endl;
ASSERT_EQ(cpid, waitpid(cpid, &ws, __WALL));
}
#endif // OS_LINUX
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment