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...