5

I am launching a command using system api (I am ok with using this api with C/C++). The command I pass may hang at times and hence I would like to kill after certain timeout.

Currently I am using it as:

system("COMMAND");

I want to use it something like this:

Run a command using a system independent API (I don't want to use CreateProcess since it is for Windows only) Kill the process if it does not exit after 'X' Minutes.

ANjaNA
  • 1,404
  • 2
  • 16
  • 29
Destructor
  • 3,154
  • 7
  • 32
  • 51
  • Is the process that you want to execute something that you've done? If it is, why does it hang? Can you put a timeout in there? what system api do you want to use? – Jaques May 14 '15 at 08:57
  • I am using adb command, adb wait-for-device. What if no device gets connected/or the device does not come up. I want to kill that adb wait-for-device command and give an error message to user. – Destructor May 14 '15 at 09:04
  • 1
    You can easly write a portable header file to invoke system commands. There is no cross-platform alternative I can think of. – Imobilis May 14 '15 at 09:07
  • 2
    Firstly, there is no such language as C/C++, those are two very distinct languages. Then, there is no support built into either of those two languages for what you want, so you will have to resort to OS-specific measures to achieve the goal. – Ulrich Eckhardt May 14 '15 at 09:07
  • 1
    On linux you can use `ps -A | grep "yourprocessname"` to have the task id to kill, and then kill it – LPs May 14 '15 at 09:14
  • Ulrich Eckhardt: I meant I am good with the solution in either C/C++ Malina In windows I can use createProcess, what about linux? – Destructor May 14 '15 at 09:33
  • Tried typing "linux start process" into the starting page of your interwebz? ;) – Ulrich Eckhardt May 14 '15 at 09:52

5 Answers5

5

Since system() is a platform-specific call, there cannot be a platform-independent way of solving your problem. However, system() is a POSIX call, so if it is supported on any given platform, the rest of the POSIX API should be as well. So, one way to solve your problem is to use fork() and kill().

There is a complication in that system() invokes a shell, which will probably spawn other processes, and I presume you want to kill all of them, so one way to do that is to use a process group. The basic idea is use fork() to create another process, place it in its own process group, and kill that group if it doesn't exit after a certain time.

A simple example - the program forks; the child process sets its own process group to be the same as its process ID, and uses system() to spawn an endless loop. The parent process waits 10 seconds then kills the process group, using the negative value of the child process PID. This will kill the forked process and any children of that process (unless they have changed their process group.)

Since the parent process is in a different group, the kill() has no effect on it.

#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <stdio.h>

int main() {
    pid_t pid;

    pid = fork();
    if(pid == 0) { // child process
        setpgid(getpid(), getpid());
        system("while true ; do echo xx ; sleep 5; done");
    } else {   // parent process
        sleep(10);
        printf("Sleep returned\n");
        kill(-pid, SIGKILL);
        printf("killed process group %d\n", pid);
    }
    exit(0);
}
Clyde
  • 7,389
  • 5
  • 31
  • 57
  • `system()` is actually part of the C standard, not part of POSIX, which is the problem -- its a very minimal interface that runs a program and waits for it to finish, with no opportunity to timeout or kill the program. – Chris Dodd Jul 30 '18 at 18:54
0

There is no standard, cross-platform system API. The hint is that they are system APIs! We're actually "lucky" that we get system, but we don't get anything other than that.

You could try to find some third-party abstraction.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
0

Check below C++ thread based attempt for linux. (not tested)

#include <iostream>
#include <string>
#include <thread>
#include <stdio.h>

using namespace std;

// execute system command and get output
// http://stackoverflow.com/questions/478898/how-to-execute-a-command-and-get-output-of-command-within-c
std::string exec(const char* cmd) {
    FILE* pipe = popen(cmd, "r");
    if (!pipe) return "ERROR";
    char buffer[128];
    std::string result = "";
    while(!feof(pipe)) {
        if(fgets(buffer, 128, pipe) != NULL)
            result += buffer;
    }
    pclose(pipe);
    return result;
}

void system_task(string& cmd){
    exec(cmd.c_str());
}

int main(){

    // system commad that takes time
    string command = "find /";

    // run the command in a separate thread
    std::thread t1(system_task, std::ref(command));

    // gives some time for the system task
    std::this_thread::sleep_for(chrono::milliseconds(200));

    // get the process id of the system task
    string query_command = "pgrep -u $LOGNAME " + command;

    string process_id = exec(query_command.c_str());

    // kill system task
    cout << "killing process " << process_id << "..." << endl;
    string kill_command = "kill " + process_id;
    exec(kill_command.c_str());

    if (t1.joinable())
        t1.join();

    cout << "continue work on main thread" << endl;

    return 0;
}
911
  • 908
  • 8
  • 16
0

I had a similar problem, in a Qt/QML development: I want to start a bash command, while continuing to process events on the Qt Loop, and killing the bash command if it takes too long.

I came up with the following class that I'm sharing here (see below), in hope it may be of some use to people with a similar problem.

Instead of calling a 'kill' command, I call a cleanupCommand supplied by the developper. Example: if I'm to call myscript.sh and want to check that it won't last run for more than 10 seconds, I'll call it the following way:

SystemWithTimeout systemWithTimeout("myScript.sh", 10, "killall myScript.sh");
systemWithTimeout.start();

Code:

class SystemWithTimeout {

private:
    bool m_childFinished = false ;
    QString m_childCommand ;
    int m_seconds ;
    QString m_cleanupCmd ;
    int m_period;

    void startChild(void) {
        int rc = system(m_childCommand.toUtf8().data());
        if (rc != 0) SYSLOG(LOG_NOTICE, "Error SystemWithTimeout startChild: system returned %d", rc);
        m_childFinished = true ;
    }

public:
    SystemWithTimeout(QString cmd, int seconds, QString cleanupCmd)
        : m_childFinished {false}, m_childCommand {cmd}, m_seconds {seconds}, m_cleanupCmd {cleanupCmd}
        { m_period = 200; }

    void setPeriod(int period) {m_period = period;}

    void start(void) ;

};

void SystemWithTimeout::start(void)
{
    m_childFinished = false ; // re-arm the boolean for 2nd and later calls to 'start'
    qDebug()<<"systemWithTimeout"<<m_childCommand<<m_seconds;

    
    QTime dieTime= QTime::currentTime().addSecs(m_seconds);

    std::thread child(&SystemWithTimeout::startChild, this);
    child.detach();

    while (!m_childFinished && QTime::currentTime() < dieTime)
    {
        QTime then = QTime::currentTime();
        QCoreApplication::processEvents(QEventLoop::AllEvents, m_period); // Process events during up to m_period ms (default: 200ms)
        QTime now = QTime::currentTime();
        int waitTime = m_period-(then.msecsTo(now)) ;
        QThread::msleep(waitTime); // wait for the remaning of the 200 ms before looping again.
    }
    if (!m_childFinished)
    {
        SYSLOG(LOG_NOTICE, "Killing command <%s> after timeout reached (%d seconds)", m_childCommand.toUtf8().data(), m_seconds);
        int rc = system(m_cleanupCmd.toUtf8().data());
        if (rc != 0) SYSLOG(LOG_NOTICE, "Error SystemWithTimeout 164: system returned %d", rc);
        m_childFinished = true ;
    }
    
}
-3

I do not know any portable way to do that in C nor C++ languages. As you ask for alternatives, I know it is possible in other languages. For example in Python, it is possible using the subprocess module.

import subprocess 
cmd = subprocess.Popen("COMMAND", shell = True)

You can then test if COMMAND has ended with

if cmd.poll() is not None:
    # cmd has finished

and you can kill it with :

cmd.terminate()

Even if you prefere to use C language, you should read the documentation for subprocess module because it explains that internally it uses CreateProcess on Windows and os.execvp on Posix systems to start the command, and it uses TerminateProcess on Windows and SIG_TERM on Posix to stop it.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252