Fork child process with timeout and capture output
You can use IO.pipe
and tell Process.spawn
to use the redirected output without the need of external gem.
Of course, only starting with Ruby 1.9.2 (and I personally recommend 1.9.3)
The following is a simple implementation used by Spinach BDD internally to capture both out and err outputs:
# stdout, stderr pipes
rout, wout = IO.pipe
rerr, werr = IO.pipe
pid = Process.spawn(command, :out => wout, :err => werr)
_, status = Process.wait2(pid)
# close write ends so we could read them
wout.close
werr.close
@stdout = rout.readlines.join("\n")
@stderr = rerr.readlines.join("\n")
# dispose the read ends of the pipes
rout.close
rerr.close
@last_exit_status = status.exitstatus
The original source is in features/support/filesystem.rb
Is highly recommended you read Ruby's own Process.spawn documentation.
Hope this helps.
PS: I left the timeout implementation as homework for you ;-)
How can I timeout a forked process that might hang?
I was able to successfully kill my exec()ed process by killing the process group, as shown as the answer to question In perl, killing child and its children when child was created using open. I modified my code as follows:
my $pid = fork;
if ($pid > 0){
eval{
local $SIG{ALRM} = sub {kill 9, -$PID; die "TIMEOUT!"};
alarm $num_secs_to_timeout;
waitpid($pid, 0);
alarm 0;
};
}
elsif ($pid == 0){
setpgrp(0,0);
exec('echo blahblah | program_of_interest');
exit(0);
}
After timeout, program_of_interest is successfully killed.
Wait at most N seconds for a fork'd child PID to finish
Figured it out, thanks for all the pointers!
Edit: fixed is_pid_alive
function to work if called from within a child as well.
The issue was that the parent's "watcher" thread was never completing, since os.waitpid
isn't a pollable/loopable function. The solution was to remove the "watcher" thread, and instead implement a polling loop that checks a pid_is_alive()
function every millisecond, like so:
def pid_is_alive(pid):
try:
os.kill(pid, 0)
os.waitpid(pid, os.WNOHANG)
os.kill(pid, 0)
except OSError:
return False
return True
def test():
X = 1000 * 1000
Y = 5000
pid = os.fork()
if pid == 0:
thread1 = MyCustomThread() #Sleeps for 30 seconds and ends.
thread1.start()
print "Started 1!"
timeout = X # say, 1000ms
while timeout > 0:
if not thread1.is_alive(): return "custom thread finished before the deadline!"
timeout -= 1
sleep(0.001)
if thread1.is_alive():
return "custom thread didn't finish before the deadline!"
thread1.stop()
exit()
else:
timeout2 = Y # say, 500ms
while timeout2 > 0:
if not pid_is_alive(pid): return "child PID finished!"
timeout2 -= 1
sleep(0.001)
if pid_is_alive(pid):
print "child PID didn't finish yet!"
exit()
print test()
print "all done!"
Waiting for child process to terminate, or not - C
Although waitpid would get you the return status of the child, its default usage would force parent to wait until the child terminates.
But your requirement (if i understood correctly) only wants parent to wait for a certain time, alarm() can be used to do that.
Then, you should use waitpid() with a specific option that returns immediately if the child has not exited yet (study the api's parameters). So if the child didn't exit, you could kill it, else you already receive its return status.
Linux: fork & execv, wait for child process hangs
I found the problem:
Within the mongoose (JSON-RPC uses mongoose) sources in the function mg_start
I found the following code
#if !defined(_WIN32) && !defined(__SYMBIAN32__)
// Ignore SIGPIPE signal, so if browser cancels the request, it
// won't kill the whole process.
(void) signal(SIGPIPE, SIG_IGN);
// Also ignoring SIGCHLD to let the OS to reap zombies properly.
(void) signal(SIGCHLD, SIG_IGN);
#endif // !_WIN32
(void) signal(SIGCHLD, SIG_IGN);
causes that
if the parent does a wait(), this call will return only when all children have exited, and then returns -1 with errno set to ECHILD."
as mentioned here in the section 5.5 Voodoo: wait and SIGCHLD.
This is also described in the man page for WAIT(2)
ERRORS [...]
ECHILD [...] (This can happen for
one's own child if the action for SIGCHLD is set to SIG_IGN.
See also the Linux Notes section about threads.)
Stupid on my part not to check the return value correctly.
Before trying
if(exitedPid == workerPid) {
I should have checked that exitedPid
is != -1
.
If I do so errno
gives me ECHILD
. If I would have known that in the first place, I would have read the man page and probably found the problem faster...
Naughty of mongoose just to mess with signal handling no matter what an application wants to do about it. Additionally mongoose does not revert the altering of signal handling when being stopped with mg_stop.
Additional info:
The code that caused this problem was changed in mongoose in September 2013 with this commit.
Timeout option is not working in nodejs spawn child process
Uh, if the timeout in spawn
isn't working.. you can use setTimeout
logic to kill the program that would have its Time Limit Exceeded
let python = spawn('python3', [`./uploads/${codefile}`]);
let timeout=setTimeout(()=>{
python.kill() //kills the program that is taking too long
deleteFiles(req.files)
res.status(200).json({
errorMessage: "Time Limit Exceeded, Please Optimised Your Code",
errorType: "Time Limit Exceeded"
});
},5000)
//I'd add a "python.on('end',some_callback)" but you seem to return the response on one chunk of data
python.stdout.on('data', (data) => {
res.status(200).json({ Success: data.toString() });
deleteFiles(req.files)
clearTimeout(timeout) //preventing the timeout from calling since it didn't take too long
return;
});
python.stderr.on('data', (data) => {
res.status(200).json({ Error: data.toString() });
deleteFiles(req.files)
clearTimeout(timeout) //preventing the timeout from calling since it didn't take too long
return;
});
python.on('error', (err) => {
// will handle Sigterm signal here
// res.status(200).json({
// errorMessage: "Time Limit Exceeded, Please Optimised Your Code",
// errorType: "Time Limit Exceeded"
// });
console.log(err)
clearTimeout(timeout) //preventing the timeout from calling since it didn't take too long
res.status(500).send("Internal Server Error");
deleteFiles(req.files);
return;
});
Using alarm() to implement process timeout
Most likely, in your version of OS, signal
call installs handler with SA_RESTART
flag set. When this flag is set, system calls are automatically restarted after signal handler.
To take control of this, instead of using obsolete and deprecated signal
, use sigaction
, and make sure to not specify SA_RESTART flag. This should fix your issue.
Related Topics
In Ruby, How Does Coerce() Actually Work
Why Is Sum So Much Faster Than Inject(:+)
Rails Respond_To Format.Js API
Cron Job Not Working in Whenever Gem
"Rake Assets:Precompile" Gives Punc Error
How to Implement a Friendship Model in Rails 3 for a Social Networking Application
Converting an Array of Objects to Activerecord::Relation
Differencebetween Raising Exceptions VS Throwing Exceptions in Ruby
Best Way to Add Comments in Erb
Best Ruby on Rails Social Networking Framework
What Is Java Interface Equivalent in Ruby
What Are Some Good Ruby-Based Web Crawlers
Is There a Better Way of Checking Nil or Length == 0 of a String in Ruby
Uninstalling All Gems Ruby 2.0.0
Ruby on Rails Console Is Hanging When Loading
Why Can't I Install Rails on Lion Using Rvm
Using Rvm on Ubuntu 12.04 to Use Rails. the Program 'Rails' Is Currently Not Installed