Starting another program via system() call

2019-03-06 04:50发布

I searched google and StackOverflow for anything similar to this but the closest ones were C code and the situation wasn't the same...

I have a program which starts another one via cstdlib's system() call to a script and everything works fine, the problem is when I have to test new code, so I stop execution (Crtl+C and kill -9 pid produce the same error), compile the new executable and try to run it again, that's when I get the message that the socket is already in use.

My program uses an acceptor and waits for connection (it handles one at a time). It can retrieve/send files and start/stop another program via one script. If I just retrieve/send files, the socket isn't locked, but if any of the system() calls is executed, then the socket gets locked and I can't run the program after I shut it down.

I really don't think this has something to do with the actual asio part, since it works properly, but this is how I defined my endpoint:

ba::ip::tcp::endpoint endpoint(ba::ip::address_v4::any(), listen_port);

Where ba is short for boost::asio. And the ssl:

ba::ip::tcp::acceptor acceptor_(*io,endpoint);
ba::ssl::context context_(*io, ba::ssl::context::sslv23);

context_.set_options( ba::ssl::context::default_workarounds |
                      ba::ssl::context::no_sslv2 |
                      ba::ssl::context::single_dh_use);
//Rest of context config

//Some more code

stream.reset( new ssl_stream(*io,context_) );

boost::system::error_code ec;
boost::system::error_code no_error; // default error_code = no error

while(true)
{
    ec = no_error;
    stream.reset( new ssl_stream(*io,context_) );
    acceptor_.accept(stream->lowest_layer(), endpoint,ec);
    if( !ec )
    {
        stream->handshake(ba::ssl::stream_base::server,ec);

        if( !ec )
        {
            operations();
        }
        //rest of the code...
}//while(true) end
acceptor_.close();
if(pid == 0)
{
    std::cout << "Child exiting!" << std::endl;
}

Where stream is:

boost::shared_ptr< boost::asio::ssl::stream<boost::asio::ip::tcp::socket> > stream;

Anyway, just to be clearer, these are the 2 situations:

First: (OK)

# ./program
(Then, I only retrieve/send files)
# kill -9 program_pid ( or Crtl+C)
# g++ .... -o program
# ./program
(Program starts normally)

Second: (ERROR)

#./program
(Use system() to start other program 1+ times)
# kill -9 program_pid (or Crtl+C)
# g++ .... -o program
# ./program
(Program fails to start, since socket "is already being used")

Funny facts:

  1. If I restart/stop (via terminal) the other program that I started in my program, the socket is freed.
  2. Even fork() -> execv() the same command, results in the socket getting "locked".
  3. Parent PID of started program is 1, not my program's PID...

I thought maybe the other program was doing something wrong, so I tried using my system call to start another program (Apache, via it's script in init.d) and I got the same result.

So I'm a little lost here in "why is this happening" and "how I can solve this"...

system() seems to have something to do with it, but I have no clue why since the call returns and the socket has nothing to do with it. Even invoking the same command with execv() from a child process (via fork()) produces the same error...

Still haven't tried popen(), but I'm sure I'll reach the same place.

EDIT: Just realised that system() is actually a fork() followed by an exec, so there was no point in pointing that out...

EDIT2: Adding more details

Following Tanner Sansbury answer, I changed my code, but still the same result...

On the operations I just read the operation and call the function related.

Then I fall on my function:

//Function body:

if (this->existBinary())
{
    io->notify_fork(boost::asio::io_service::fork_prepare);

    pid = fork();

    if( pid == 0 )
    {
        io->notify_fork(boost::asio::io_service::fork_child);

        char binChar[80];

        for(unsigned int i = 0; i < bin.size(); ++i)
        {
            binChar[i] = bin[i];
        }

        binChar[bin.size()] = '\0';

        char* bin_args[] = { binChar, "start" , NULL };
        execv( bin_args[0], bin_args );
    }
    else
    {
        io->notify_fork(boost::asio::io_service::fork_parent);

        if( pid == -1)
        {
            std::cout << "Fork error" << std::endl;
        }
        else
        {
            // running in parent, wait exec to complete
            // and return its exit status.
            int status;
            waitpid( pid, &status, 0 );
            printf("Child %d exited with status %d\n", pid, status );
        }
    }

    // returning true just for testing
    return true;
}

return false;

The existBinary() function:

bool my_class::existBinary(const std::string& filename) const
{
    std::ifstream file(filename.c_str(),std::ios_base::in | std::ios_base::binary);
    bool file_status = false;

    if(file.is_open())
    {
        file_status = true;
        file.close();
    }

    return file_status;
}

Notice that I put a cout to print when the child exited, but it shows nothing... :(

I'll try moving the acceptor to be a class member and closing it on the child.

Again, the socket IS FREED when I restart/stop THE OTHER PROGRAM...

1条回答
Animai°情兽
2楼-- · 2019-03-06 05:32

In short, based on the observed behaviors, the source of the problem is likely resulting from fork()'s behavior, as the child process will inherit a copy of the acceptor's open file descriptor. Closing the acceptor from within the child should resolve the problem.


When fork() occurs, the child process inherits copies of the parent's set of open file descriptors. Here is the relevant excerpt from the fork documentation:

The child inherits copies of the parent's set of open file descriptors. Each file descriptor in the child refers to the same open file description as the corresponding file descriptor in the parent. This means that the two descriptors share open file status flags, current file offset, and signal-driven I/O attributes .

However, Boost.Asio has internal file descriptors that will also be duplicated. The io_service needs to be prepared for fork(), and notified after fork() occurs. Boost.Asio document's the proper fork usage. Per the documentation, it is the program's responsibility to manage any file descriptors accessible via Boost.Asio's public API during a fork:

// Inform the io_service that we are about to fork. The io_service cleans
// up any internal resources, such as threads, that may interfere with
// forking.
io_service_.notify_fork(boost::asio::io_service::fork_prepare);

if (0 == fork())
{
  // Inform the io_service that the fork is finished and that this is the
  // child process. The io_service uses this opportunity to create any
  // internal file descriptors that must be private to the new process.
  io_service_.notify_fork(boost::asio::io_service::fork_child);

  // The child won't be accepting new connections, so we can close the
  // acceptor. It remains open in the parent.
  acceptor_.close();
  system(...);
  ...
}
else
{
  // Inform the io_service that the fork is finished (or failed) and that
  // this is the parent process. The io_service uses this opportunity to
  // recreate any internal resources that were cleaned up during
  // preparation for the fork.
  io_service_.notify_fork(boost::asio::io_service::fork_parent);
  ...
}

See Boost.Asio's process per connection for an example where the process forks from within a completion handler.

查看更多
登录 后发表回答