You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by be...@apache.org on 2014/04/04 01:26:11 UTC

git commit: Adds a "setup" function to process::subprocess.

Repository: mesos
Updated Branches:
  refs/heads/master 7601fd1e1 -> b23460e98


Adds a "setup" function to process::subprocess.

Adds the ability run a "setup" function to process::subprocess within
the forked child context, after fork and before exec.

NOTE: Such lambda must not contain any async unsafe code. For details
on async safety, see POSIX.1-2004 on async-signal-safe functions, also
referenced in the signal man-pages:
http://man7.org/linux/man-pages/man7/signal.7.html.

Review: https://reviews.apache.org/r/19259


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/b23460e9
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/b23460e9
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/b23460e9

Branch: refs/heads/master
Commit: b23460e982e7ac8dbb0012343194c74202ce8988
Parents: 7601fd1
Author: Till Toenshoff <to...@me.com>
Authored: Thu Apr 3 16:24:54 2014 -0700
Committer: Benjamin Hindman <be...@gmail.com>
Committed: Thu Apr 3 16:24:54 2014 -0700

----------------------------------------------------------------------
 .../libprocess/include/process/subprocess.hpp   |  18 ++-
 3rdparty/libprocess/src/subprocess.cpp          |  17 ++-
 .../libprocess/src/tests/subprocess_tests.cpp   | 143 +++++++++++++++++++
 3 files changed, 171 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/b23460e9/3rdparty/libprocess/include/process/subprocess.hpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/include/process/subprocess.hpp b/3rdparty/libprocess/include/process/subprocess.hpp
index 6c51c0b..e9d7b31 100644
--- a/3rdparty/libprocess/include/process/subprocess.hpp
+++ b/3rdparty/libprocess/include/process/subprocess.hpp
@@ -8,6 +8,7 @@
 
 #include <process/future.hpp>
 
+#include <stout/lambda.hpp>
 #include <stout/memory.hpp>
 #include <stout/option.hpp>
 #include <stout/os.hpp>
@@ -39,7 +40,8 @@ private:
   Subprocess() : data(new Data()) {}
   friend Try<Subprocess> subprocess(
       const std::string& command,
-      const std::map<std::string, std::string>& environment);
+      const Option<std::map<std::string, std::string> >& environment,
+      const Option<lambda::function<int()> >& setup);
 
   struct Data
   {
@@ -65,13 +67,19 @@ private:
 };
 
 
-// Environment is combined with the OS environment and overrides where
-// necessary.
+// The Environment is combined with the OS environment and overrides
+// where necessary.
+// The setup function is run after forking but before executing the
+// command. If the return value of that setup function is non-zero,
+// then that is what the subprocess status will be;
+// status = setup && command.
+// NOTE: Take extra care about the design of the setup function as it
+// must not contain any async unsafe code.
 // TODO(dhamon): Add an option to not combine the two environments.
 Try<Subprocess> subprocess(
     const std::string& command,
-    const std::map<std::string, std::string>& environment =
-      std::map<std::string, std::string>());
+    const Option<std::map<std::string, std::string> >& environment = None(),
+    const Option<lambda::function<int()> >& setup = None());
 
 } // namespace process {
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/b23460e9/3rdparty/libprocess/src/subprocess.cpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/src/subprocess.cpp b/3rdparty/libprocess/src/subprocess.cpp
index e09c808..27a4355 100644
--- a/3rdparty/libprocess/src/subprocess.cpp
+++ b/3rdparty/libprocess/src/subprocess.cpp
@@ -105,7 +105,8 @@ Envp::~Envp()
 // Runs the provided command in a subprocess.
 Try<Subprocess> subprocess(
     const string& command,
-    const map<string, string>& environment)
+    const Option<map<string, string> >& environment,
+    const Option<lambda::function<int()> >& setup)
 {
   // Create pipes for stdin, stdout, stderr.
   // Index 0 is for reading, and index 1 is for writing.
@@ -127,7 +128,12 @@ Try<Subprocess> subprocess(
     return ErrnoError("Failed to create pipe");
   }
 
-  internal::Envp envp(environment);
+  // We need to do this construction before doing the fork as it
+  // might not be async-safe.
+  // TODO(tillt): Consider optimizing this to not pass an empty map
+  // into the constructor or even further to use execl instead of
+  // execle once we have no user supplied environment.
+  internal::Envp envp(environment.get(map<string, string>()));
 
   pid_t pid;
   if ((pid = fork()) == -1) {
@@ -160,6 +166,13 @@ Try<Subprocess> subprocess(
     os::close(stdoutPipe[1]);
     os::close(stderrPipe[1]);
 
+    if (setup.isSome()) {
+      int status = setup.get()();
+      if (status != 0) {
+        _exit(status);
+      }
+    }
+
     execle("/bin/sh", "sh", "-c", command.c_str(), (char*) NULL, envp());
 
     ABORT("Failed to execle '/bin sh -c ", command.c_str(), "'\n");

http://git-wip-us.apache.org/repos/asf/mesos/blob/b23460e9/3rdparty/libprocess/src/tests/subprocess_tests.cpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/src/tests/subprocess_tests.cpp b/3rdparty/libprocess/src/tests/subprocess_tests.cpp
index 9413ecc..8d85c05 100644
--- a/3rdparty/libprocess/src/tests/subprocess_tests.cpp
+++ b/3rdparty/libprocess/src/tests/subprocess_tests.cpp
@@ -16,6 +16,7 @@
 #include <stout/gtest.hpp>
 #include <stout/list.hpp>
 #include <stout/os/read.hpp>
+#include <stout/path.hpp>
 
 using namespace process;
 
@@ -379,3 +380,145 @@ TEST(Subprocess, environmentOverride)
   ASSERT_TRUE(WIFEXITED(status));
   ASSERT_EQ(0, WEXITSTATUS(status));
 }
+
+
+int setupChdir(const string& directory)
+{
+  // Keep everything async-signal safe.
+  if (::chdir(directory.c_str()) == -1) {
+    return errno;
+  }
+
+  return 0;
+}
+
+
+int setupSetsid(void)
+{
+  // Keep everything async-signal safe.
+  if (::setsid() == -1) {
+    return errno;
+  }
+
+  return 0;
+}
+
+
+TEST(Subprocess, setup)
+{
+  Clock::pause();
+
+  Try<string> directory = os::mkdtemp();
+  ASSERT_SOME(directory);
+
+  // chdir().
+  Try<Subprocess> s = subprocess(
+      "echo hello world > file",
+      None(),
+      lambda::bind(&setupChdir, directory.get()));
+
+  ASSERT_SOME(s);
+
+  // Advance time until the internal reaper reaps the subprocess.
+  while (s.get().status().isPending()) {
+    Clock::advance(Seconds(1));
+    Clock::settle();
+  }
+
+  AWAIT_ASSERT_READY(s.get().status());
+  ASSERT_SOME(s.get().status().get());
+
+  // Make sure 'file' is there and contains 'hello world'.
+  const string& path = path::join(directory.get(), "file");
+  EXPECT_TRUE(os::exists(path));
+  EXPECT_SOME_EQ("hello world\n", os::read(path));
+
+  os::rmdir(directory.get());
+
+  // setsid().
+  s = subprocess(
+      "sleep 60",
+      None(),
+      lambda::bind(&setupSetsid));
+
+  ASSERT_SOME(s);
+
+  // Verify the process is in a different session.
+  EXPECT_NE(getsid(0), getsid(s.get().pid()));
+
+  kill(s.get().pid(), SIGTERM);
+
+  // Advance time until the internal reaper reaps the subprocess.
+  while (s.get().status().isPending()) {
+    Clock::advance(Seconds(1));
+    Clock::settle();
+  }
+
+  AWAIT_ASSERT_READY(s.get().status());
+  ASSERT_SOME(s.get().status().get());
+
+  int status = s.get().status().get().get();
+  ASSERT_TRUE(WIFSIGNALED(status));
+  ASSERT_EQ(SIGTERM, WTERMSIG(status));
+
+  Clock::resume();
+}
+
+
+int setupStatus(int ret)
+{
+  return ret;
+}
+
+
+TEST(Subprocess, setupStatus)
+{
+  Clock::pause();
+
+  // Exit 0 && setup 1.
+  Try<Subprocess> s = subprocess(
+      "exit 0",
+      None(),
+      lambda::bind(&setupStatus, 1));
+
+  ASSERT_SOME(s);
+
+  // Advance time until the internal reaper reaps the subprocess.
+  while (s.get().status().isPending()) {
+    Clock::advance(Seconds(1));
+    Clock::settle();
+  }
+
+  AWAIT_ASSERT_READY(s.get().status());
+  ASSERT_SOME(s.get().status().get());
+
+  int status = s.get().status().get().get();
+
+  // Verify we received the setup returned value instead of the
+  // command status.
+  ASSERT_EQ(1, WEXITSTATUS(status));
+
+  // Exit 1 && setup 0.
+  s = subprocess(
+      "exit 1",
+      None(),
+      lambda::bind(&setupStatus, 0));
+
+  ASSERT_SOME(s);
+
+  // Advance time until the internal reaper reaps the subprocess.
+  while (s.get().status().isPending()) {
+    Clock::advance(Seconds(1));
+    Clock::settle();
+  }
+
+  AWAIT_ASSERT_READY(s.get().status());
+  ASSERT_SOME(s.get().status().get());
+
+  status = s.get().status().get().get();
+
+  // Verify we received the command status.
+  ASSERT_EQ(1, WEXITSTATUS(status));
+
+  Clock::resume();
+}