Posted to tcl by sebres at Wed Dec 05 16:47:49 GMT 2018view pretty

## ================================================================================
## Following code shows the unexpected behavior: no EOF on pipe of the child process,
## if still one child started from.
## PoC: 
##   Initial process starts a child process asynchronously and waits for end of file,
##   the result of the script execution depends on first parameter (WITH_DELAY_SUBPROC):
##      0. This child is alone and does not start another child, then EOF is signaled
##         in the main process.
##      1. This child started still one process, then NO EOF signaled
##         in the main process (one see a timeout after 2 seconds).
##
##   Reproducible for unix/windows for all tcl-versions > 8.5
##
## Conclusion:
##   The handle to pipe seems to be inherited from sub-child.
## ================================================================================

lassign $::argv WITH_DELAY_SUBPROC WITH_TIMEOUT WITH_TICKS
if {$WITH_DELAY_SUBPROC eq ""} {set WITH_DELAY_SUBPROC 1}
if {$WITH_TIMEOUT eq ""} {set WITH_TIMEOUT 1}
if {$WITH_TICKS eq ""} {set WITH_TICKS 0}

# Scripts:
set script [file join [if [info exists env(TEMP)] {set env(TEMP)} {set _ /tmp}] test-script-sock-12.2.tcl]
set script1 [file root $script]--2.tcl
if {$WITH_DELAY_SUBPROC} {
  set f [open $script1 w]
  puts $f {
    after 5000 exit
    vwait forever
  }
  close $f
}

set f [open $script w]
puts $f {puts "testing start"}
if {$WITH_DELAY_SUBPROC} {
  puts $f {puts "  sub-process ..."}
  puts $f [list exec [info nameofexecutable] $script1 &]
}
puts $f {
  puts "testing end, exit"
  exit
}
close $f

# Logger:
variable start [clock milliseconds]
proc log {s} {
  variable start
  puts "+[format %04d [expr {[clock milliseconds] - $start}]] $s"
}

# Readable event from script, "waiting" for EOF (pipe closed):
proc readfromscript {p} {
  # wait for end of p:
  variable done
  log "=== read, done:$done, gets:[gets $p], eof:[eof $p] ==="
  if {![eof $p]} return
  fileevent $p readable {}
  # Pipe closed - process exited, give few time (0.1s) to fulfill the end.
  variable x
  set x "process ended"
  log "======== EOF -> AFTER ======="
  after 100 [list set done 1]
  #close $p
}

set done 0

log start
set p [open "|[list [info nameofexecutable] $script]" w+]
fconfigure $p -buffering none -blocking 0
fileevent $p readable [list readfromscript $p]

set x none
if {$WITH_TIMEOUT} {
  set tmr [after 2000 {set x timeout; set done -1}]
}
if {$WITH_TICKS} {
  proc ticker {} {
    variable done
    variable p
    log "... tick: done:$done, eof:[eof $p]"
    after 50 ticker
  }
  ticker
}
vwait done
log "done:$done, x:$x"
if {$WITH_TIMEOUT} {
  after cancel $tmr
}
catch {close $p}
log end.

file delete $script
file delete $script1

return

## Results:

## without sub-process (works as expected):
$ tclsh ~/test.tcl 0
+0000 start
+0002 === read, done:0, gets:testing start, eof:0 ===
+0002 === read, done:0, gets:testing end, exit, eof:0 ===
+0002 === read, done:0, gets:, eof:1 ===
+0002 ======== EOF -> AFTER =======
+0102 done:1, x:process ended
+0102 end.

## with sub-process (no EOF on first process, the handle to pipe seems to be inherited from sub-child):
$ tclsh ~/test.tcl 1
+0000 start
+0002 === read, done:0, gets:testing start, eof:0 ===
+0002 === read, done:0, gets:  sub-process ..., eof:0 ===
+0002 === read, done:0, gets:testing end, exit, eof:0 ===
+2000 done:-1, x:timeout
+2000 end.

Comments

Posted by sebres at Wed Dec 05 17:06:24 GMT 2018 [text] [code]

if timeout disabled (WITH_TIMEOUT = 0), it will also signal EOF on pipe in main-process, but firstly when the child sub-process ends (so after 5 seconds): $ tclsh ~/test.tcl 1 0 +0000 start +0002 === read, done:0, gets:testing start, eof:0 === +0002 === read, done:0, gets: sub-process ..., eof:0 === +0002 === read, done:0, gets:testing end, exit, eof:0 === +5005 === read, done:0, gets:, eof:1 === +5005 ======== EOF -> AFTER ======= +5105 done:1, x:process ended +5105 end.