Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Implement reforks by forking the application, to save the app exec time
authorMartin Quinson <martin.quinson@ens-rennes.fr>
Sun, 26 Mar 2023 19:56:46 +0000 (21:56 +0200)
committerMartin Quinson <martin.quinson@ens-rennes.fr>
Sun, 26 Mar 2023 19:58:06 +0000 (21:58 +0200)
Instead of forking from the checker and exec()ing the application, we
now fork+exec an application that we use as a proxy to the real
application process.

When we need a new application process, we fork it from the proxy,
which is much faster as it's already initialized.

The extra complexity is when the socket closes abruptly on the Checker
side, which means that the application died. We cannot waitpid() on
the app directly, as it's our grandchild. So, we have to ask the proxy
to do the waitpid for us and return the status.

src/mc/api/RemoteApp.cpp
src/mc/api/RemoteApp.hpp
src/mc/remote/AppSide.cpp
src/mc/remote/AppSide.hpp
src/mc/remote/Channel.cpp
src/mc/remote/Channel.hpp
src/mc/remote/CheckerSide.cpp
src/mc/remote/CheckerSide.hpp
src/mc/remote/mc_protocol.h

index b53ef17..fc496ad 100644 (file)
 
 #include <algorithm>
 #include <array>
+#include <limits.h>
 #include <memory>
 #include <numeric>
 #include <string>
-
 #include <sys/ptrace.h>
+#include <sys/un.h>
 #include <sys/wait.h>
 
 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(mc_Session, mc, "Model-checker session");
@@ -29,12 +30,38 @@ XBT_LOG_EXTERNAL_CATEGORY(mc_global);
 
 namespace simgrid::mc {
 
-RemoteApp::RemoteApp(const std::vector<char*>& args, bool need_memory_introspection) : app_args_(args)
+static char master_socket_name[65] = {};
+static void cleanup_master_socket()
 {
-  checker_side_ = std::make_unique<simgrid::mc::CheckerSide>(app_args_, need_memory_introspection);
+  if (master_socket_name[0] != '\0')
+    unlink(master_socket_name);
+  master_socket_name[0] = '\0';
+}
 
-  if (need_memory_introspection)
+RemoteApp::RemoteApp(const std::vector<char*>& args, bool need_memory_introspection) : app_args_(args)
+{
+  if (need_memory_introspection) {
+    checker_side_     = std::make_unique<simgrid::mc::CheckerSide>(app_args_, need_memory_introspection);
     initial_snapshot_ = std::make_shared<simgrid::mc::Snapshot>(0, page_store_, *checker_side_->get_remote_memory());
+  } else {
+    master_socket_ = socket(AF_LOCAL, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
+    xbt_assert(master_socket_ != -1, "Cannot create the master socket: %s", strerror(errno));
+
+    struct sockaddr_un serv_addr = {};
+    serv_addr.sun_family         = AF_LOCAL;
+    snprintf(serv_addr.sun_path, 64, "/tmp/simgrid-mc-%d", getpid());
+    strcpy(master_socket_name, serv_addr.sun_path);
+    auto addr_size = offsetof(struct sockaddr_un, sun_path) + strlen(serv_addr.sun_path);
+
+    xbt_assert(bind(master_socket_, (struct sockaddr*)&serv_addr, addr_size) >= 0,
+               "Cannot bind the master socket to %s: %s.", serv_addr.sun_path, strerror(errno));
+    atexit(cleanup_master_socket);
+
+    xbt_assert(listen(master_socket_, SOMAXCONN) >= 0, "Cannot listen to the master socket: %s.", strerror(errno));
+
+    application_factory_ = std::make_unique<simgrid::mc::CheckerSide>(app_args_, need_memory_introspection);
+    checker_side_        = application_factory_->clone(master_socket_);
+  }
 }
 
 RemoteApp::~RemoteApp()
@@ -46,9 +73,7 @@ RemoteApp::~RemoteApp()
 void RemoteApp::restore_initial_state()
 {
   if (initial_snapshot_ == nullptr) { // No memory introspection
-    // We need to destroy the existing CheckerSide before creating the new one, or libevent gets crazy
-    checker_side_.reset(nullptr);
-    checker_side_.reset(new simgrid::mc::CheckerSide(app_args_, false));
+    checker_side_ = application_factory_->clone(master_socket_);
   } else
     initial_snapshot_->restore(*checker_side_->get_remote_memory());
 }
@@ -76,21 +101,22 @@ void RemoteApp::get_actors_status(std::map<aid_t, ActorState>& whereto) const
   //
   // CheckerSide                  AppSide
   // send ACTORS_STATUS ---->
-  //                    <----- send ACTORS_STATUS_REPLY
-  //                    <----- send `N` `s_mc_message_actors_status_one_t` structs
-  //                    <----- send `M` `s_mc_message_simcall_probe_one_t` structs
+  //                    <----- send ACTORS_STATUS_REPLY_COUNT
+  //                    <----- send `N` ACTORS_STATUS_REPLY_TRANSITION (s_mc_message_actors_status_one_t)
+  //                    <----- send `M` ACTORS_STATUS_REPLY_SIMCALL (s_mc_message_simcall_probe_one_t)
   checker_side_->get_channel().send(MessageType::ACTORS_STATUS);
 
   s_mc_message_actors_status_answer_t answer;
   ssize_t answer_size = checker_side_->get_channel().receive(answer);
   xbt_assert(answer_size != -1, "Could not receive message");
   xbt_assert(answer_size == sizeof answer, "Broken message (size=%zd; expected %zu)", answer_size, sizeof answer);
-  xbt_assert(answer.type == MessageType::ACTORS_STATUS_REPLY,
-             "Received unexpected message %s (%i); expected MessageType::ACTORS_STATUS_REPLY (%i)",
-             to_c_str(answer.type), (int)answer.type, (int)MessageType::ACTORS_STATUS_REPLY);
+  xbt_assert(answer.type == MessageType::ACTORS_STATUS_REPLY_COUNT,
+             "Received unexpected message %s (%i); expected MessageType::ACTORS_STATUS_REPLY_COUNT (%i)",
+             to_c_str(answer.type), (int)answer.type, (int)MessageType::ACTORS_STATUS_REPLY_COUNT);
 
   // Message sanity checks
-  xbt_assert(answer.count >= 0, "Received an ACTOR_STATUS_REPLY message with an actor count of '%d' < 0", answer.count);
+  xbt_assert(answer.count >= 0, "Received an ACTORS_STATUS_REPLY_COUNT message with an actor count of '%d' < 0",
+             answer.count);
 
   std::vector<s_mc_message_actors_status_one_t> status(answer.count);
   if (answer.count > 0) {
@@ -166,10 +192,10 @@ Transition* RemoteApp::handle_simcall(aid_t aid, int times_considered, bool new_
   s_mc_message_simcall_execute_answer_t answer;
   ssize_t s = checker_side_->get_channel().receive(answer);
   xbt_assert(s != -1, "Could not receive message");
+  xbt_assert(s > 0 && answer.type == MessageType::SIMCALL_EXECUTE_REPLY,
+             "%d Received unexpected message %s (%i); expected MessageType::SIMCALL_EXECUTE_REPLY (%i)", getpid(),
+             to_c_str(answer.type), (int)answer.type, (int)MessageType::SIMCALL_EXECUTE_REPLY);
   xbt_assert(s == sizeof answer, "Broken message (size=%zd; expected %zu)", s, sizeof answer);
-  xbt_assert(answer.type == MessageType::SIMCALL_EXECUTE_ANSWER,
-             "Received unexpected message %s (%i); expected MessageType::SIMCALL_EXECUTE_ANSWER (%i)",
-             to_c_str(answer.type), (int)answer.type, (int)MessageType::SIMCALL_EXECUTE_ANSWER);
 
   if (new_transition) {
     std::stringstream stream(answer.buffer.data());
index 285d0d4..693a220 100644 (file)
@@ -29,6 +29,8 @@ private:
   std::unique_ptr<CheckerSide> checker_side_;
   PageStore page_store_{500};
   std::shared_ptr<simgrid::mc::Snapshot> initial_snapshot_;
+  std::unique_ptr<CheckerSide> application_factory_; // when no meminfo, create checker_side_ by cloning this one
+  int master_socket_ = -1;
 
   const std::vector<char*> app_args_;
 
index a70b095..be9826a 100644 (file)
@@ -28,6 +28,8 @@
 #include <sys/ptrace.h>
 #include <sys/socket.h>
 #include <sys/types.h>
+#include <sys/un.h>
+#include <sys/wait.h>
 
 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(mc_client, mc, "MC client logic");
 XBT_LOG_EXTERNAL_CATEGORY(mc_global);
@@ -114,7 +116,7 @@ void AppSide::handle_simcall_execute(const s_mc_message_simcall_execute_t* messa
 
   // Finish the RPC from the server: return a serialized observer, to build a Transition on Checker side
   s_mc_message_simcall_execute_answer_t answer = {};
-  answer.type = MessageType::SIMCALL_EXECUTE_ANSWER;
+  answer.type                                  = MessageType::SIMCALL_EXECUTE_REPLY;
   std::stringstream stream;
   if (actor->simcall_.observer_ != nullptr) {
     actor->simcall_.observer_->serialize(stream);
@@ -150,6 +152,42 @@ void AppSide::handle_finalize(const s_mc_message_int_t* msg) const
   if (terminate_asap)
     ::_Exit(0);
 }
+void AppSide::handle_fork(const s_mc_message_int_t* msg)
+{
+  int pid = fork();
+  xbt_assert(pid >= 0, "Could not fork application sub-process: %s.", strerror(errno));
+
+  if (pid == 0) { // Child
+    int sock = socket(AF_LOCAL, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
+
+    struct sockaddr_un addr = {};
+    addr.sun_family         = AF_LOCAL;
+    snprintf(addr.sun_path, 64, "/tmp/simgrid-mc-%lu", msg->value);
+    auto addr_size = offsetof(struct sockaddr_un, sun_path) + strlen(addr.sun_path);
+
+    xbt_assert(connect(sock, (struct sockaddr*)&addr, addr_size) >= 0,
+               "Cannot connect to Checker on /tmp/simgrid-mc-%lu: %s.", msg->value, strerror(errno));
+
+    channel_.reset_socket(sock);
+
+    s_mc_message_int_t answer = {};
+    answer.type               = MessageType::FORK_REPLY;
+    answer.value              = getpid();
+    xbt_assert(channel_.send(answer) == 0, "Could not send response to WAIT_CHILD_REPLY: %s", strerror(errno));
+  }
+}
+void AppSide::handle_wait_child(const s_mc_message_int_t* msg)
+{
+  int status;
+  errno = 0;
+  waitpid(msg->value, &status, 0);
+  xbt_assert(errno == 0, "Cannot wait on behalf of the checker: %s.", strerror(errno));
+
+  s_mc_message_int_t answer = {};
+  answer.type               = MessageType::WAIT_CHILD_REPLY;
+  answer.value              = status;
+  xbt_assert(channel_.send(answer) == 0, "Could not send response to WAIT_CHILD: %s", strerror(errno));
+}
 void AppSide::handle_need_meminfo()
 {
   this->need_memory_info_                  = true;
@@ -166,6 +204,7 @@ void AppSide::handle_actors_status() const
   std::vector<s_mc_message_actors_status_one_t> status;
   for (auto const& [aid, actor] : actor_list) {
     s_mc_message_actors_status_one_t one = {};
+    one.type                             = MessageType::ACTORS_STATUS_REPLY_TRANSITION;
     one.aid                              = aid;
     one.enabled                          = mc::actor_is_enabled(actor);
     one.max_considered                   = actor->simcall_.observer_->get_max_consider();
@@ -173,7 +212,7 @@ void AppSide::handle_actors_status() const
   }
 
   struct s_mc_message_actors_status_answer_t answer = {};
-  answer.type             = MessageType::ACTORS_STATUS_REPLY;
+  answer.type                                       = MessageType::ACTORS_STATUS_REPLY_COUNT;
   answer.count            = static_cast<int>(status.size());
 
   xbt_assert(channel_.send(answer) == 0, "Could not send ACTORS_STATUS_REPLY msg");
@@ -194,6 +233,7 @@ void AppSide::handle_actors_status() const
     for (int times_considered = 0; times_considered < max_considered; times_considered++) {
       std::stringstream stream;
       s_mc_message_simcall_probe_one_t probe;
+      probe.type = MessageType::ACTORS_STATUS_REPLY_SIMCALL;
 
       if (actor->simcall_.observer_ != nullptr) {
         actor->simcall_.observer_->prepare(times_considered);
@@ -232,7 +272,7 @@ void AppSide::handle_actors_maxpid() const
 void AppSide::handle_messages()
 {
   while (true) { // Until we get a CONTINUE message
-    XBT_DEBUG("Waiting messages from model-checker");
+    XBT_DEBUG("Waiting messages from the model-checker");
 
     std::array<char, MC_MESSAGE_LENGTH> message_buffer;
     ssize_t received_size = channel_.receive(message_buffer.data(), message_buffer.size());
@@ -247,15 +287,15 @@ void AppSide::handle_messages()
 
     const s_mc_message_t* message = (s_mc_message_t*)message_buffer.data();
     switch (message->type) {
+      case MessageType::CONTINUE:
+        assert_msg_size("MESSAGE_CONTINUE", s_mc_message_t);
+        return;
+
       case MessageType::DEADLOCK_CHECK:
         assert_msg_size("DEADLOCK_CHECK", s_mc_message_t);
         handle_deadlock_check(message);
         break;
 
-      case MessageType::CONTINUE:
-        assert_msg_size("MESSAGE_CONTINUE", s_mc_message_t);
-        return;
-
       case MessageType::SIMCALL_EXECUTE:
         assert_msg_size("SIMCALL_EXECUTE", s_mc_message_simcall_execute_t);
         handle_simcall_execute((s_mc_message_simcall_execute_t*)message_buffer.data());
@@ -266,6 +306,16 @@ void AppSide::handle_messages()
         handle_finalize((s_mc_message_int_t*)message_buffer.data());
         break;
 
+      case MessageType::FORK:
+        assert_msg_size("FORK", s_mc_message_int_t);
+        handle_fork((s_mc_message_int_t*)message_buffer.data());
+        break;
+
+      case MessageType::WAIT_CHILD:
+        assert_msg_size("WAIT_CHILD", s_mc_message_int_t);
+        handle_wait_child((s_mc_message_int_t*)message_buffer.data());
+        break;
+
       case MessageType::NEED_MEMINFO:
         assert_msg_size("NEED_MEMINFO", s_mc_message_t);
         handle_need_meminfo();
index d4e68aa..5dcb934 100644 (file)
@@ -33,6 +33,8 @@ private:
   void handle_deadlock_check(const s_mc_message_t* msg) const;
   void handle_simcall_execute(const s_mc_message_simcall_execute_t* message) const;
   void handle_finalize(const s_mc_message_int_t* msg) const;
+  void handle_fork(const s_mc_message_int_t* msg);
+  void handle_wait_child(const s_mc_message_int_t* msg);
   void handle_need_meminfo();
   void handle_actors_status() const;
   void handle_actors_maxpid() const;
index 37a3c7b..6160412 100644 (file)
@@ -46,14 +46,12 @@ int Channel::send(const void* message, size_t size) const
 ssize_t Channel::receive(void* message, size_t size) const
 {
   ssize_t res = recv(this->socket_, message, size, 0);
-  if (res != -1) {
-    if (static_cast<size_t>(res) >= sizeof(int) && is_valid_MessageType(*static_cast<int*>(message))) {
-      XBT_DEBUG("Receive %s (requested %zu; received %zd)", to_c_str(*static_cast<MessageType*>(message)), size, res);
-    } else {
-      XBT_DEBUG("Receive %zd bytes", res);
-    }
+  xbt_assert(res != -1, "Channel::receive failure: %s", strerror(errno));
+  if (static_cast<size_t>(res) >= sizeof(int) && is_valid_MessageType(*static_cast<const int*>(message))) {
+    XBT_DEBUG("Receive %s (requested %zu; received %zd at %p)", to_c_str(*static_cast<const MessageType*>(message)),
+              size, res, message);
   } else {
-    XBT_ERROR("Channel::receive failure: %s", strerror(errno));
+    XBT_DEBUG("Receive %zd bytes", res);
   }
   return res;
 }
index fa270b1..c9e1aeb 100644 (file)
@@ -50,6 +50,7 @@ public:
     return this->receive(&m, sizeof(M));
   }
 
+  // Socket handling
   int get_socket() const { return socket_; }
   void reset_socket(int socket) { socket_ = socket; }
 };
index 9d82879..a19f093 100644 (file)
@@ -119,10 +119,8 @@ static void wait_application_process(pid_t pid)
              errno);
 }
 
-void CheckerSide::setup_events()
+void CheckerSide::setup_events(bool socket_only)
 {
-  if (base_ != nullptr)
-    event_base_free(base_.get());
   auto* base = event_base_new();
   base_.reset(base);
 
@@ -150,32 +148,38 @@ void CheckerSide::setup_events()
       this);
   event_add(socket_event_, nullptr);
 
-  signal_event_ = event_new(
-      base, SIGCHLD, EV_SIGNAL | EV_PERSIST,
-      [](evutil_socket_t sig, short events, void* arg) {
-        auto checker = static_cast<simgrid::mc::CheckerSide*>(arg);
-        if (events == EV_SIGNAL) {
-          if (sig == SIGCHLD)
-            checker->handle_waitpid();
-          else
-            xbt_die("Unexpected signal: %d", sig);
-        } else {
-          xbt_die("Unexpected event");
-        }
-      },
-      this);
-  event_add(signal_event_, nullptr);
+  if (socket_only) {
+    signal_event_ = nullptr;
+  } else {
+    signal_event_ = event_new(
+        base, SIGCHLD, EV_SIGNAL | EV_PERSIST,
+        [](evutil_socket_t sig, short events, void* arg) {
+          auto checker = static_cast<simgrid::mc::CheckerSide*>(arg);
+          if (events == EV_SIGNAL) {
+            if (sig == SIGCHLD)
+              checker->handle_waitpid();
+            else
+              xbt_die("Unexpected signal: %d", sig);
+          } else {
+            xbt_die("Unexpected event");
+          }
+        },
+        this);
+    event_add(signal_event_, nullptr);
+  }
 }
 
+/* When this constructor is called, no other checkerside exists */
 CheckerSide::CheckerSide(const std::vector<char*>& args, bool need_memory_info) : running_(true)
 {
   // Create an AF_LOCAL socketpair used for exchanging messages between the model-checker process (ancestor)
   // and the application process (child)
   int sockets[2];
-  xbt_assert(socketpair(AF_LOCAL, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != -1, "Could not create socketpair");
+  xbt_assert(socketpair(AF_LOCAL, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != -1, "Could not create socketpair: %s",
+             strerror(errno));
 
   pid_ = fork();
-  xbt_assert(pid_ >= 0, "Could not fork model-checked process");
+  xbt_assert(pid_ >= 0, "Could not fork application process");
 
   if (pid_ == 0) { // Child
     ::close(sockets[1]);
@@ -187,12 +191,12 @@ CheckerSide::CheckerSide(const std::vector<char*>& args, bool need_memory_info)
   ::close(sockets[0]);
   channel_.reset_socket(sockets[1]);
 
-  setup_events();
-  if (need_memory_info)
+  setup_events(false); /* we need a signal handler too */
+  if (need_memory_info) {
+    // setup ptrace and sync with the app
     wait_application_process(pid_);
 
-  // Request the initial memory on need
-  if (need_memory_info) {
+    // Request the initial memory on need
     channel_.send(MessageType::NEED_MEMINFO);
     s_mc_message_need_meminfo_reply_t answer;
     ssize_t answer_size = channel_.receive(answer);
@@ -213,8 +217,41 @@ CheckerSide::~CheckerSide()
 {
   event_del(socket_event_);
   event_free(socket_event_);
-  event_del(signal_event_);
-  event_free(signal_event_);
+  if (signal_event_ != nullptr) {
+    event_del(signal_event_);
+    event_free(signal_event_);
+  }
+}
+
+/* This constructor is called when cloning a checkerside to get its application to fork away */
+CheckerSide::CheckerSide(int socket, CheckerSide* child_checker)
+    : channel_(socket), running_(true), child_checker_(child_checker)
+{
+  setup_events(true); // We already have a signal handled in that case
+
+  s_mc_message_int_t answer;
+  ssize_t s = get_channel().receive(answer);
+  xbt_assert(s != -1, "Could not receive answer to FORK_REPLY");
+  xbt_assert(s == sizeof answer, "Broken message (size=%zd; expected %zu)", s, sizeof answer);
+  xbt_assert(answer.type == MessageType::FORK_REPLY,
+             "Received unexpected message %s (%i); expected MessageType::FORK_REPLY (%i)", to_c_str(answer.type),
+             (int)answer.type, (int)MessageType::FORK_REPLY);
+  pid_ = answer.value;
+
+  wait_for_requests();
+}
+
+std::unique_ptr<CheckerSide> CheckerSide::clone(int master_socket)
+{
+  s_mc_message_int_t m = {};
+  m.type               = MessageType::FORK;
+  m.value              = getpid();
+  xbt_assert(get_channel().send(m) == 0, "Could not ask the app to fork on need.");
+
+  int sock = accept(master_socket, nullptr /* I know who's connecting*/, nullptr);
+  xbt_assert(sock > 0, "Cannot accept the incomming connection of the forked app: %s.", strerror(errno));
+
+  return std::make_unique<CheckerSide>(sock, this);
 }
 
 void CheckerSide::finalize(bool terminate_asap)
@@ -323,14 +360,14 @@ bool CheckerSide::handle_message(const char* buffer, ssize_t size)
       break;
 
     default:
-      xbt_die("Unexpected message from model-checked application");
+      xbt_die("Unexpected message from the application");
   }
   return true;
 }
 
 void CheckerSide::wait_for_requests()
 {
-  /* Resume the application */
+  XBT_DEBUG("Resume the application");
   if (get_channel().send(MessageType::CONTINUE) != 0)
     throw xbt::errno_error();
   clear_memory_cache();
@@ -345,56 +382,79 @@ void CheckerSide::clear_memory_cache()
     remote_memory_->clear_cache();
 }
 
-void CheckerSide::handle_waitpid()
+void CheckerSide::handle_dead_child(int status)
 {
-  XBT_DEBUG("Check for wait event");
-  int status;
-  pid_t pid;
-  while ((pid = waitpid(-1, &status, WNOHANG)) != 0) {
-    if (pid == -1) {
-      if (errno == ECHILD) { // No more children:
-        xbt_assert(not this->running(), "Inconsistent state");
-        break;
-      } else {
-        xbt_die("Could not wait for pid: %s", strerror(errno));
-      }
-    }
-
-    if (pid == get_pid()) {
-      // From PTRACE_O_TRACEEXIT:
+  // From PTRACE_O_TRACEEXIT:
 #ifdef __linux__
-      if (status >> 8 == (SIGTRAP | (PTRACE_EVENT_EXIT << 8))) {
-        unsigned long eventmsg;
-        xbt_assert(ptrace(PTRACE_GETEVENTMSG, pid, 0, &eventmsg) != -1, "Could not get exit status");
-        status = static_cast<int>(eventmsg);
-        if (WIFSIGNALED(status)) {
-          this->terminate();
-          Exploration::get_instance()->report_crash(status);
-        }
-      }
+  if (status >> 8 == (SIGTRAP | (PTRACE_EVENT_EXIT << 8))) {
+    unsigned long eventmsg;
+    xbt_assert(ptrace(PTRACE_GETEVENTMSG, pid_, 0, &eventmsg) != -1, "Could not get exit status");
+    status = static_cast<int>(eventmsg);
+    if (WIFSIGNALED(status)) {
+      this->terminate();
+      Exploration::get_instance()->report_crash(status);
+    }
+  }
 #endif
 
-      // We don't care about non-lethal signals, just reinject them:
-      if (WIFSTOPPED(status)) {
-        XBT_DEBUG("Stopped with signal %i", (int)WSTOPSIG(status));
-        errno = 0;
+  // We don't care about non-lethal signals, just reinject them:
+  if (WIFSTOPPED(status)) {
+    XBT_DEBUG("Stopped with signal %i", (int)WSTOPSIG(status));
+    errno = 0;
 #ifdef __linux__
-        ptrace(PTRACE_CONT, pid, 0, WSTOPSIG(status));
+    ptrace(PTRACE_CONT, pid_, 0, WSTOPSIG(status));
 #elif defined BSD
-        ptrace(PT_CONTINUE, pid, (caddr_t)1, WSTOPSIG(status));
+    ptrace(PT_CONTINUE, pid_, (caddr_t)1, WSTOPSIG(status));
 #endif
-        xbt_assert(errno == 0, "Could not PTRACE_CONT");
-      }
+    xbt_assert(errno == 0, "Could not PTRACE_CONT: %s", strerror(errno));
+  }
+
+  else if (WIFSIGNALED(status)) {
+    this->terminate();
+    Exploration::get_instance()->report_crash(status);
+  } else if (WIFEXITED(status)) {
+    XBT_DEBUG("Child process is over");
+    this->terminate();
+  }
+}
 
-      else if (WIFSIGNALED(status)) {
-        this->terminate();
-        Exploration::get_instance()->report_crash(status);
-      } else if (WIFEXITED(status)) {
-        XBT_DEBUG("Child process is over");
-        this->terminate();
+void CheckerSide::handle_waitpid()
+{
+  XBT_DEBUG("Check for wait event");
+
+  if (child_checker_ == nullptr) { // Wait directly
+    int status;
+    pid_t pid;
+    while ((pid = waitpid(-1, &status, WNOHANG)) != 0) {
+      if (pid == -1) {
+        if (errno == ECHILD) { // No more children:
+          xbt_assert(not this->running(), "Inconsistent state");
+          break;
+        } else {
+          xbt_die("Could not wait for pid: %s", strerror(errno));
+        }
       }
+
+      if (pid == get_pid())
+        handle_dead_child(status);
     }
+
+  } else { // Ask our proxy to wait for us
+
+    s_mc_message_int_t request = {};
+    request.type               = MessageType::WAIT_CHILD;
+    request.value              = pid_;
+    xbt_assert(child_checker_->get_channel().send(request) == 0,
+               "Could not ask my child to waitpid its child for me: %s", strerror(errno));
+
+    s_mc_message_int_t answer;
+    ssize_t answer_size = child_checker_->get_channel().receive(answer);
+    xbt_assert(answer_size != -1, "Could not receive message");
+    xbt_assert(answer.type == MessageType::WAIT_CHILD_REPLY,
+               "The received message is not the WAIT_CHILD_REPLY I was expecting but of type %s",
+               to_c_str(answer.type));
+    xbt_assert(answer_size == sizeof answer, "Broken message (size=%zd; expected %zu)", answer_size, sizeof answer);
+    handle_dead_child(answer.value);
   }
 }
-
 } // namespace simgrid::mc
index 17fb33c..a3cb6a6 100644 (file)
@@ -26,12 +26,17 @@ class CheckerSide {
   Channel channel_;
   bool running_ = false;
   pid_t pid_;
+  // When forking (no meminfo), the real app is our grandchild. In this case,
+  // child_checker_ is a CheckerSide to our child that can waitpid our grandchild on our behalf
+  CheckerSide* child_checker_ = nullptr;
 
-  void setup_events(); // Part of the initialization
+  void setup_events(bool socket_only); // Part of the initialization
   void clear_memory_cache();
-  void handle_waitpid();
+  void handle_dead_child(int status); // Launched when the dying child is the PID we follow
+  void handle_waitpid();              // Launched when receiving a sigchild
 
 public:
+  explicit CheckerSide(int socket, CheckerSide* child_checker);
   explicit CheckerSide(const std::vector<char*>& args, bool need_memory_introspection);
   ~CheckerSide();
 
@@ -49,6 +54,9 @@ public:
   void break_loop() const;
   void wait_for_requests();
 
+  /* Create a new CheckerSide by forking the currently existing one, and connect it through the master_socket */
+  std::unique_ptr<CheckerSide> clone(int master_socket);
+
   /** Ask the application to run post-mortem analysis, and maybe to stop ASAP */
   void finalize(bool terminate_asap = false);
 
index f22cfd1..920f43f 100644 (file)
 // ***** Messages
 namespace simgrid::mc {
 
-XBT_DECLARE_ENUM_CLASS(MessageType, NONE, NEED_MEMINFO, NEED_MEMINFO_REPLY, CONTINUE, IGNORE_HEAP, UNIGNORE_HEAP,
-                       IGNORE_MEMORY, STACK_REGION, REGISTER_SYMBOL, DEADLOCK_CHECK, DEADLOCK_CHECK_REPLY, WAITING,
-                       SIMCALL_EXECUTE, SIMCALL_EXECUTE_ANSWER, ASSERTION_FAILED, ACTORS_STATUS, ACTORS_STATUS_REPLY,
-                       ACTORS_MAXPID, ACTORS_MAXPID_REPLY, FINALIZE, FINALIZE_REPLY);
+XBT_DECLARE_ENUM_CLASS(MessageType, NONE, NEED_MEMINFO, NEED_MEMINFO_REPLY, FORK, FORK_REPLY, WAIT_CHILD,
+                       WAIT_CHILD_REPLY, CONTINUE, IGNORE_HEAP, UNIGNORE_HEAP, IGNORE_MEMORY, STACK_REGION,
+                       REGISTER_SYMBOL, DEADLOCK_CHECK, DEADLOCK_CHECK_REPLY, WAITING, SIMCALL_EXECUTE,
+                       SIMCALL_EXECUTE_REPLY, ASSERTION_FAILED, ACTORS_STATUS, ACTORS_STATUS_REPLY_COUNT,
+                       ACTORS_STATUS_REPLY_SIMCALL, ACTORS_STATUS_REPLY_TRANSITION, ACTORS_MAXPID, ACTORS_MAXPID_REPLY,
+                       FINALIZE, FINALIZE_REPLY);
 } // namespace simgrid::mc
 
 constexpr unsigned MC_MESSAGE_LENGTH                 = 512;
@@ -106,6 +108,7 @@ struct s_mc_message_actors_status_answer_t {
 };
 struct s_mc_message_actors_status_one_t { // an array of `s_mc_message_actors_status_one_t[count]` is sent right after
                                           // after a `s_mc_message_actors_status_answer_t`
+  simgrid::mc::MessageType type;
   aid_t aid;
   bool enabled;
   int max_considered;
@@ -114,6 +117,7 @@ struct s_mc_message_actors_status_one_t { // an array of `s_mc_message_actors_st
 // Answer from an actor to the question "what are you about to run?"
 struct s_mc_message_simcall_probe_one_t { // a series of `s_mc_message_simcall_probe_one_t`
                                           // is sent right after `s_mc_message_actors_status_one_t[]`
+  simgrid::mc::MessageType type;
   std::array<char, SIMCALL_SERIALIZATION_BUFFER_SIZE> buffer;
 };