How to fork an external program and get back its stdout and stderr without blocking and WITH a timeout!
proc _exec { prog { var "" } { uniq "" } { i 0 } { to 5 } } { if { ! [ regexp {^file\d+$} $prog ] } { set uniq _exec-[ clock seconds ][ clock clicks ] set tmpfile /tmp/$uniq set tmpvar ::$uniq set $tmpvar [ list ] if { [ catch { set fid [ open "|/usr/bin/env LD_PRELOAD= $prog 2>$tmpfile" ] fconfigure $fid -blocking off fconfigure $fid -buffersize 50000 fconfigure $fid -buffering full } err ] } { file delete -force $tmpfile return -code error "_exec: $err" } } else { set fid $prog set tmpfile /tmp/$uniq set tmpvar ::$uniq } set ret [ read $fid ] if { ($i / 10) >= $to } { if { ! [ string length $ret ] } { set ret timed_out } } ;## first condition is because we only ever were ;## reading 4096 chars ever. bizarre. if { [ string length $ret ] && \ ! [ file size $tmpfile ] && \ ! [ eof $fid ] } { append $tmpvar $ret incr i if { $to <= 10 } { set to 1 } after 100 [ list _exec $fid $var $uniq $i $to ] } elseif { [ string length $ret ] || \ [ file size $tmpfile ] || \ [ string length [ set $tmpvar ] ] } { set ret [ set $tmpvar ]$ret set pids [ pid $fid ] catch { ::close $fid } catch { ::unset $tmpvar } foreach pid $pids { catch { ::exec kill -KILL $pid } } set fid [ open $tmpfile r ] set err [ read $fid [ file size $tmpfile ] ] catch { ::close $fid } catch { ::unset $tmpvar } file delete -force $tmpfile if { [ string length $var ] } { set ::$var [ list $ret $err ] } } else { incr i after 100 [ list _exec $fid $var $uniq $i ] } }
Usage example:
# of course, you really want to trace the variable, # not vwait on it! _exec "curl http://www.ligo.caltech.edu" foo vwait ::foo puts $::foo
This seems a little heavy handed in that as soon as any output (either stdout or stderr) is available then the program is killed. I would think that using a filevent handler might be a little more robust (and you could still have a timeout that DID kill it. - bbh
You're right. I made it a bit less heavy handed... specifically to deal with "ps", which seems to write its output in 4k buffers. Stderr handling could maybe do with a bit more. Anyway, it's supposed to be heavyhanded first, and as it develops bugs I'll keep coming back and ease it up a bit...