#include <iostream> // cout, cerr, endl
#include <unistd.h> // usleep, fork, exec
#include <cstdlib> // exit
#include <cstdio> // sscanf, perror
#include <sys/wait.h> // kill, waitpid, SIGKILL
#include <errno.h> // errno
#include <string.h> // strerror
// How often to check the child's status (ms)
const int STEP = 5;
// ms is in milliseconds
void sleep_ms(int ms)
{
usleep(ms * 1000); // 0.001ms sleep
}
// debug
void print_args(int argc, char **argv)
{
for (int i = 3; i < argc; i++)
std::cerr << argv[i] << " ";
}
//argv[1] - hard-timeout
//argv[2] - soft-timeout
//argv[3] - executable
//argv[4] - args... to executable
int main(int argc, char ** argv)
{
if (argc < 3)
{
std::cout << "Usage: " << argv[0] << " hard_timeout soft_timeout ";
std::cout << "exe_to_run args_for_exe ....\n";
std::cout << "where " << std::endl;
std::cout << " hard_timeout - How long to wait until terminating child.\n";
std::cout << " soft_timeout - How long to wait until child is now inefficient.\n";
return 1;
}
int hard_timeout; // kill the program at this point
int soft_timeout; // allow the program to run after this point
std::sscanf(argv[1], "%d", &hard_timeout);
std::sscanf(argv[2], "%d", &soft_timeout);
// Program to exec
char *exe = argv[3];
int status; // child's return code
int duration = 0; // how long the child has been running for
pid_t pid = fork();
// child
if (pid == 0)
{
int execReturn = execvp(exe, argv + 3);
std::cerr << "Failure! execv error code = " << execReturn << std::endl;
perror("execvp");
exit(1);
}
// parent
int result = 0; // return code for OS
if (pid < 0) // fork failed
{
std::cerr << "Failed to fork" << std::endl;
return 1;
}
else
{
// long long will allow this code to work with 32-bit and 64-bit systems
long long waitval = waitpid(pid, &status, WNOHANG);
// Continue to wait until child finishes, or the wait time expires
while ((duration <= hard_timeout) && !WIFEXITED(status))
{
// The wait failed for some reason
if (waitval == -1)
{
std::cerr << "!!!!! Something bad happened running ";
print_args(argc, argv);
std::cerr << " (" << strerror(errno) << ") !!!!!\n";
return 2;
}
sleep_ms(STEP); // don't busy wait
duration += STEP;
waitval = waitpid(pid, &status, WNOHANG);
}
// Child exited normally
if (WIFEXITED(status))
{
// Inefficient
if (duration > soft_timeout)
{
std::cout << "\n********** Test took " << duration << " ms. (Inefficient) **********\n";
result = 3;
}
else // Efficient
{
//std::cout << "\nTest completed within efficient time limit. (" << soft_timeout << " ms";
//std::cout << ", actual " << duration << " ms)\n";
}
}
else // Child didn't finish, kill it
{
int err; // kill success/fail
result = 4; // code indicating child took too long
err = kill(pid, SIGKILL);
if (err)
{
std::cerr << "kill failed!" << std::endl;
perror("kill");
result = 5; // kill failed
}
std::cout << "\n********** Test took too long to complete (over " << hard_timeout << "ms). **********\n";
}
// At this point, the child has finished or has been terminated (hopefully)
}
return result;
}
Simple tester app:
#include <unistd.h> // usleep
#include <stdlib.h> // atoi
void sleep_ms(int ms)
{
usleep(ms * 1000);
}
int main(int argc, char **argv)
{
int ms = 1000;
if (argc > 1)
ms = atoi(argv[1]);
sleep_ms(ms);
return 0;
}