0

Reading the answer to How to end C++ code, I learned that calling exit from C++ code is bad. But what if I have forked a child process which must end somewhere and is so deep down the call stack that passing down its exit code to main would be impossible?

I found a few alternatives how to do that -- admittedly, this has become a bit lengthy, but bear with me:

#include <sys/stat.h>
#include <unistd.h>
#include <sys/wait.h>

#include <cstdlib>

#include <memory>
#include <string>
#include <iostream>

thread_local char const* thread_id{"main"};

struct DtorTest {
    std::string where{};
     DtorTest(void) = default;
     DtorTest(std::string _where) : where{std::move(_where)} {}
    ~DtorTest(void) {
        std::cerr << __func__ << " in " << thread_id << ", origin " << where << std::endl;
    }
};

std::unique_ptr< DtorTest > freeatexit{nullptr};

pid_t
fork_this(pid_t (*fork_option)(void))
{
    DtorTest test(__func__);
    return fork_option();
}

pid_t
fork_option0(void)
{
    pid_t pid;
    if ((pid = fork()))
        return pid;
    thread_id = "child";
    DtorTest test(__func__);
    std::exit(EXIT_SUCCESS);
}

pid_t
fork_option1(void)
{
    pid_t pid;
    if ((pid = fork()))
        return pid;
    thread_id = "child";
    DtorTest test(__func__);
    std::_Exit(EXIT_SUCCESS);
}

pid_t
fork_option2(void)
{
    pid_t pid;
    if ((pid = fork()))
        return pid;
    {
    thread_id = "child";
    DtorTest test(__func__);
    }
    std::_Exit(EXIT_SUCCESS);
}

pid_t
fork_option3(void)
{
    pid_t pid;
    if ((pid = fork()))
        return pid;
    thread_id = "child";
    DtorTest test(__func__);
    throw std::exception();
}

int main(int argc, char const *argv[])
{
    int status;
    const int option = (argc > 1) ? std::stoi(argv[1]) : 0;
    pid_t pid;
    freeatexit = std::unique_ptr< DtorTest >(new DtorTest(__func__));


    switch (option) {
      case 0:
        pid = fork_this(fork_option0);
        break;
      case 1:
        pid = fork_this(fork_option1);
        break;
      case 2:
        pid = fork_this(fork_option2);
        break;
      case 3:
        try {
            pid = fork_this(fork_option3);
        } catch (std::exception) {
            return EXIT_SUCCESS;
        }
        break;
      case 4:
        try {
            pid = fork_this(fork_option3);
        } catch (std::exception) {
            std::_Exit(EXIT_SUCCESS);
        }
        break;
      default:
        return EXIT_FAILURE;
    }

    waitpid(pid, &status, 0);

    return status;
}

Option 0

Possibly the worst:

./a.out 0
~DtorTest in main, origin fork_this
~DtorTest in child, origin main
~DtorTest in main, origin main

The problem is, that the destructor of test in fork_option0 is not called, because std::exit simply ignores any object with automatic storage. Worse, the unique_ptr destructor is called twice.

Option 1

./a.out 1
~DtorTest in main, origin fork_this
~DtorTest in main, origin main

Same problem with the destructor in fork_option1 because std::_Exit ignores automatic storage as well. At least the unique_ptr destructor is called only once.

Option 2

This seems to work, the destructors are called correctly.

./a.out 2
~DtorTest in main, origin fork_this
~DtorTest in child, origin fork_option2
~DtorTest in main, origin main

Option 3

This is the nearest approximation to the return from main advice, however it has several problems:

./a.out 3
~DtorTest in main, origin fork_this
~DtorTest in child, origin fork_option3
~DtorTest in child, origin fork_this
~DtorTest in child, origin main
~DtorTest in main, origin main

Although the destructors in fork_option3 are correctly invoked, two double frees happen. First the unique_ptr, second the object in fork_this.

Option 4

./a.out 4
~DtorTest in main, origin fork_this
~DtorTest in child, origin fork_option3
~DtorTest in child, origin fork_this
~DtorTest in main, origin main

Slightly better than option three, because the double free of the unique_ptr is gone. However the objects in fork_this are still double free'd.

So what is the proper way to exit/end a child process?

From the above experiments it would seem that option2 works best. However, I might have missed other problems with std::_Exit (see How to end C++ code)

Community
  • 1
  • 1
user1978011
  • 3,419
  • 25
  • 38
  • Why would you not simply call `return`? – Galik May 19 '15 at 23:08
  • @Galik The child's work is done, it should cease to exist. – user1978011 May 19 '15 at 23:09
  • 1
    The child's work is not done if the destructors of its automatic variables have not been called. – Galik May 19 '15 at 23:12
  • @Galik I can agree with that. However, I don't want the child process to continue in main. – user1978011 May 19 '15 at 23:14
  • You might care to review [How to end C++ code?](http://stackoverflow.com/questions/30250934) but you may have a different scenario from what's assumed there, and you may need to look at one or more of the cross-referenced questions to suit your situation. – Jonathan Leffler May 20 '15 at 01:02
  • I see it is old, but just a note here: there is no `double free` in the code. Each process has its own address space, and `free`s the data inside that. Each of those two instance's are `free`d once. Only have to be careful with resources other than memory. – Gábor Buella Mar 12 '16 at 19:58

1 Answers1

0

This is the traditional pattern of fork.

#include <sys/stat.h>
#include <unistd.h>
#include <sys/wait.h>
#include <cstdlib>

#include <iostream>
#include <fstream>

struct DtorTest {
    ~DtorTest(void) { std::cout << "d'tor never runs\n"; }
};

int
child(void)
{
    // only child
    DtorTest dtortest;  // D'tor never runs
    std::ofstream fout("inchild.txt");  // file not flushed
    fout << "this is in the child\n";
    return 0;
}

int
main(void)
{
    pid_t pid;
    if ((pid = fork()))
      int status;
      waitpid(pid, &status, 0);
      return status;
   } else {
      return child();
   }
}

Don't use extern "C" for the system include files. If you need that, then you must be using an ancient compiler, and all bets are off.

Mark Lakata
  • 19,989
  • 5
  • 106
  • 123
  • My example is a simplified version. The fork happens deeper in the call stack such that returning the exit code to main is not an option. – user1978011 May 20 '15 at 06:45
  • Please have a look at option 3 & 4 from my updated post. It shows that your proposed solution doesn't work. – user1978011 May 20 '15 at 09:57