Skip to content
Advertisement

Process management code behaves different on Linux and Windows – Why?

(Context) I’m developing a cross-platform (Windows and Linux) application for distributing files among computers, based on BitTorrent Sync. I’ve made it in C# already, and am now porting to C++ as an exercise.

BTSync can be started in API mode, and for such, one must start the ‘btsync‘ executable passing the name and location of a config file as arguments.

At this point, my greatest problem is getting my application to deal with the executable. I’ve come to found Boost.Process when searching for a cross-platform process management library, and decided to give it a try. It seems that v0.5 is it’s latest working release, as some evidence suggests, and it can be infered there’s a number of people using it.

I implemented the library as follows (relevant code only):

File: test.hpp

namespace testingBoostProcess
{
    class Test
    {
        void StartSyncing();
    };
}

File: Test.cpp

#include <string>
#include <vector>
#include <iostream>
#include <boost/process.hpp>
#include <boost/process/mitigate.hpp>
#include "test.hpp"

using namespace std;
using namespace testingBoostProcess;
namespace bpr = ::boost::process;

#ifdef _WIN32
const vector<wstring> EXE_NAME_ARGS = { L"btsync.exe", L"/config", L"conf.json" };
#else
const vector<string> EXE_NAME_ARGS = { "btsync", "--config", "conf.json" };
#endif

void Test::StartSyncing()
{
    cout << "Starting Server...";
    try
    {
        bpr::child exeServer = bpr::execute(bpr::initializers::set_args(EXE_NAME_ARGS),
            bpr::initializers::throw_on_error(), bpr::initializers::inherit_env());

        auto exitStatus = bpr::wait_for_exit(exeServer);    //  type will be either DWORD or int
        int exitCode = BOOST_PROCESS_EXITSTATUS(exitStatus);
        cout << " ok" << "tstatus: " << exitCode << "n";
    }
    catch (const exception& excStartExeServer)
    {
        cout << "n" << "Error: " << excStartExeServer.what() << "n";
    }
}


(Problem) On Windows, the above code will start btsync and wait (block) until the process is terminated (either by using Task Manager or by the API’s shutdown method), just like desired.
But on Linux, it finishes execution immediately after starting the process, as if wait_for_exit() isn’t there at all, though the btsync process isn’t terminated.

In an attempt to see if that has something to do with the btsync executable itself, I replaced it by this simple program:

File: Fake-Btsync.cpp

#include <cstdio>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define SLEEP Sleep(20000)
#include <Windows.h>
#else
#include <unistd.h>
#define SLEEP sleep(20)
#endif
using namespace std;

int main(int argc, char* argv[])
{
    for (int i = 0; i < argc; i++)
    {
        printf(argv[i]);
        printf("n");
    }
    SLEEP;
    return 0;
}


When used with this program, instead of the original btsync downloaded from the official website, my application works as desired. It will block for 20 seconds and then exit.

Question: What is the reason for the described behavior? The only thing I can think of is that btsync restarts itself on Linux. But how to confirm that? Or what else could it be?

Update: All I needed to do was to know about what forking is and how it works, as pointed in sehe’s answer (thanks!).


Question 2: If I use the System Monitor to send an End command to the child process ‘Fake-Btsync‘ while my main application is blocked, wait_for_exit() will throw an exception saying:

waitpid(2) failed: No child processes

Which is a different behavior than on Windows, where it simply says “ok” and terminates with status 0.

Update 2: sehe’s answer is great, but didn’t quite address Question 2 in a way I could actually understand. I’ll write a new question about that and post the link here.

Advertisement

Answer

The problem is your assumption about btsync. Let’s start it:

./btsync 
By using this application, you agree to our Privacy Policy, Terms of Use and End User License Agreement.
http://www.bittorrent.com/legal/privacy
http://www.bittorrent.com/legal/terms-of-use
http://www.bittorrent.com/legal/eula

BitTorrent Sync forked to background. pid = 24325. default port = 8888

So, that’s the whole story right there: BitTorrent Sync forked to background. Nothing more. Nothing less. If you want to, btsync --help tells you to pass --nodaemon.

Testing Process Termination

Let’s pass --nodaemon run btsync using the test program. In a separate subshell, let’s kill the child btsync process after 5 seconds:

sehe@desktop:/tmp$ (./test; echo exit code $?) & (sleep 5; killall btsync)& time wait
[1] 24553
[2] 24554
By using this application, you agree to our Privacy Policy, Terms of Use and End User License Agreement.
http://www.bittorrent.com/legal/privacy
http://www.bittorrent.com/legal/terms-of-use
http://www.bittorrent.com/legal/eula

[20141029 10:51:16.344] total physical memory 536870912 max disk cache 2097152
[20141029 10:51:16.344] Using IP address 192.168.2.136
[20141029 10:51:16.346] Loading config file version 1.4.93
[20141029 10:51:17.389] UPnP: Device error "http://192.168.2.1:49000/l2tpv3.xml": (-2) 
[20141029 10:51:17.407] UPnP: ERROR mapping TCP port 43564 -> 192.168.2.136:43564. Deleting mapping and trying again: (403) Unknown result code (UPnP protocol violation?)
[20141029 10:51:17.415] UPnP: ERROR removing TCP port 43564: (403) Unknown result code (UPnP protocol violation?)
[20141029 10:51:17.423] UPnP: ERROR mapping TCP port 43564 -> 192.168.2.136:43564: (403) Unknown result code (UPnP protocol violation?)
[20141029 10:51:21.428] Received shutdown request via signal 15
[20141029 10:51:21.428] Shutdown. Saving config sync.dat
Starting Server... ok   status: 0
exit code 0
[1]-  Done                    ( ./test; echo exit code $? )
[2]+  Done                    ( sleep 5; killall btsync )

real    0m6.093s
user    0m0.003s
sys 0m0.026s

No problem!

A Better Fake Btsync

This should still be portable and be (much) better behaved when killed/terminated/interrupted:

#include <boost/asio/signal_set.hpp>
#include <boost/asio.hpp>
#include <iostream>

int main(int argc, char* argv[])
{
    boost::asio::io_service is;
    boost::asio::signal_set ss(is);
    boost::asio::deadline_timer timer(is, boost::posix_time::seconds(20));
    ss.add(SIGINT);
    ss.add(SIGTERM);

    auto stop = [&]{ 
        ss.cancel();    // one of these will be redundant
        timer.cancel();
    };

    ss.async_wait([=](boost::system::error_code ec, int sig){ 
            std::cout << "Signal received: " << sig << " (ec: '" << ec.message() << "')n"; 
            stop();
        });
    timer.async_wait([&](boost::system::error_code ec){
            std::cout << "Timer: '" << ec.message() << "'n";
            stop();
        });

    std::copy(argv, argv+argc, std::ostream_iterator<std::string>(std::cout, "n"));
    is.run();

    return 0;
}

You can test whether it is well-behaved

(./btsync --nodaemon; echo exit code $?) & (sleep 5; killall btsync)& time wait

The same test can be run with “official” btsync and “fake” btsync. Output on my linux box:

sehe@desktop:/tmp$ (./btsync --nodaemon; echo exit code $?) & (sleep 5; killall btsync)& time wait
[1] 24654
[2] 24655
./btsync
--nodaemon
Signal received: 15 (ec: 'Success')
Timer: 'Operation canceled'
exit code 0
[1]-  Done                    ( ./btsync --nodaemon; echo exit code $? )
[2]+  Done                    ( sleep 5; killall btsync )

real    0m5.014s
user    0m0.001s
sys 0m0.014s
User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement