Thread/fork/tk nightmares

i’m working on an tk ap that drives alot of complex code, some of which is not
even ruby, and essitially gives you running progress report. i’ve had no end
to the woes one can get into by mixing threads and forks with tk. some
combinations even seem to hang X… in any case, the cause of the forks is
mostly open3 and i’m considering this alternative:

def spawn command
  ipath = tmpfifo
  opath = tmpfifo
  epath = tmpfifo

  cmd = "#{ command } < #{ ipath } 1> #{ opath } 2> #{ epath } &"
  system cmd

  i = open ipath, 'w'
  o = open opath, 'r'
  e = open epath, 'r'

  [i,o,e]
end

def tmpfifo
  path = nil
  42.times do |i|
    tpath = File.join(Dir.tmpdir, "#{ $$ }.#{ rand }.#{ i }")
    system "mkfifo #{ tpath }"
    next unless $? == 0
    path = tpath
    at_exit{ File.unlink path rescue STDERR.puts "rm <#{ path }> failed" }
    break
  end
  raise "could not generate tmpfifo" unless path
  path
end

obviously this is a non-portable alternative - but then again so is open3. can
anyone see any large drawbacks to this? it plays well with threads and tk
since one can:

i,o,e = spawn ‘sh’
i.puts long_running_command

Thread.new do
while((out = o.gets))

update widgets with out

end
end

Thread.new do
while((err = e.gets))

update widgets with err

end
end

-a

···

EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
PHONE :: 303.497.6469
ADDRESS :: E/GC2 325 Broadway, Boulder, CO 80305-3328
URL :: Solar-Terrestrial Physics Data | NCEI
TRY :: for l in ruby perl;do $l -e “print "\x3a\x2d\x29\x0a"”;done
===============================================================================

Nice idea IMHO.
Funny, I had to write something similar yesterday to get rid of open3
warnings that were triggered when publishing my objects with drb because
of its forks. This is what I did. It is much simpler as I do not need to
read stdout while the spawned process runs, nor write to its stdin. I only
need to get stderr and stdout separately.

def runrun(rcmd)

Get a temp file

tmp = Tempfile.new(“runrun”)

Redirect stderr into temp file

po = IO.popen(“#{rcmd} 2>#{tmp.path}”)
stdo = po.readlines
po.close

Reopen temp file and read STDERR

tmp.open
stdr = tmp.readlines

Close and unlink temp file

tmp.close!

return STDOUT and STDERR

[stdo, stdr]
end

Regards,
Xavier.

···

On Wed, 24 Mar 2004 20:13:46 -0700, Ara.T.Howard wrote:

i’m working on an tk ap that drives alot of complex code, some of which is not
even ruby, and essitially gives you running progress report. i’ve had no end
to the woes one can get into by mixing threads and forks with tk. some
combinations even seem to hang X… in any case, the cause of the forks is
mostly open3 and i’m considering this alternative:

def spawn command
  ipath = tmpfifo
  opath = tmpfifo
  epath = tmpfifo

  cmd = "#{ command } < #{ ipath } 1> #{ opath } 2> #{ epath } &"
  system cmd

  i = open ipath, 'w'
  o = open opath, 'r'
  e = open epath, 'r'

  [i,o,e]
end

def tmpfifo
  path = nil
  42.times do |i|
    tpath = File.join(Dir.tmpdir, "#{ $$ }.#{ rand }.#{ i }")
    system "mkfifo #{ tpath }"
    next unless $? == 0
    path = tpath
    at_exit{ File.unlink path rescue STDERR.puts "rm <#{ path }> failed" }
    break
  end
  raise "could not generate tmpfifo" unless path
  path
end

obviously this is a non-portable alternative - but then again so is open3. can
anyone see any large drawbacks to this? it plays well with threads and tk
since one can:

i,o,e = spawn ‘sh’
i.puts long_running_command

Thread.new do
while((out = o.gets))

update widgets with out

end
end

Thread.new do
while((err = e.gets))

update widgets with err

end
end

-a

i also saw that the ruby-dev suumary 23175-23213 by Masayoshi Takahashi spoke
of related issues…


[ruby-dev:23212] warning: fork terminates thread

Tanaka Akira proposed to suppress "fork terminates thread"
warnings on using folk with thread as follows:

  % ruby -ve 'Thread.new { sleep }; pid = fork {}; Process.wait pid'
  ruby 1.9.0 (2004-03-22) [i686-linux]
  -e:1: warning: fork terminates thread at -e:1

Rationale for the proposal is:

  * If the warning is for chang of specification of fork in
    Ruby 1.8, it should be suppressed on Ruby 1.9.

  * fork must be used when system and popen are inadequant.
    In such a case, the warning is unsolicited.

  * Even if the user doesn't use thread explicitly, some libraries
    such like timeout use thread implicitly.

Matz agreed to do it.

my situation is that i have a long running peice of code that i need to start
in a thread (so the rest of a tk app can continue)… the code drives about 30
minutes of external processes for which i need the stdout and stderr. this is
made more difficult by the fact that i need to send multiple commands to the
external processes depending on their outputs (sort of expect like) and thus
cannot close the stdin of any of the processes… i’ve tried using select,
threads doing normal reads, etc - but any combination seems to result in the
thread getting all the output from the processes at once - not bit by bit.
this is obviously very undsirable for a gui which is supposed to be monitoring
the progress of these processes… starting the external processes in the
background via system and redirecting their stdin, stdout, stderr to temporary
fifos eliminates the fork and my problems… this little program, although
contrived, illustrates the problem:

if it uses open3 the output comes all at once, not incrementally

~/eg/ruby > spawn.rb open3
o @ 1080227752.76307: “42\n42\n”

if it uses spawn the output comes incrementally

~/eg/ruby > spawn.rb spawn
o @ 1080227758.40758: “42\n”
o @ 1080227760.00291: “42\n”

the code (sorry for length)

···

On Thu, 25 Mar 2004, Xavier wrote:

Nice idea IMHO.
Funny, I had to write something similar yesterday to get rid of open3
warnings that were triggered when publishing my objects with drb because
of its forks. This is what I did. It is much simpler as I do not need to
read stdout while the spawned process runs, nor write to its stdin. I only
need to get stderr and stdout separately.

def runrun(rcmd)

Get a temp file

tmp = Tempfile.new(“runrun”)

Redirect stderr into temp file

po = IO.popen(“#{rcmd} 2>#{tmp.path}”)
stdo = po.readlines
po.close

Reopen temp file and read STDERR

tmp.open
stdr = tmp.readlines

Close and unlink temp file

tmp.close!

return STDOUT and STDERR

[stdo, stdr]
end

Regards,
Xavier.


#!/usr/bin/env ruby
require ‘open3’
require ‘tmpdir’
require ‘io/wait’

$VERBOSE=nil

module Spawn
class << self
def spawn cmd
ipath, opath, epath = tmpfifo, tmpfifo, tmpfifo
system “#{ cmd } < #{ ipath } 1> #{ opath } 2> #{ epath } &”
[open(ipath, ‘w’), open(opath, ‘r’), open(epath, ‘r’)]
end
def tmpfifo
path = nil
42.times do |i|
tpath = File.join(Dir.tmpdir, “#{ $$ }.#{ rand }.#{ i }”)
system “mkfifo #{ tpath }”
next unless $? == 0
path = tpath
at_exit{ File.unlink(path) rescue STDERR.puts(“rm <#{ path }> failed”) }
break
end
raise “could not generate tmpfifo” unless path
path
end
end
end

mode = ARGV.shift || ‘open3’

program = <<-stmts
echo 42
sleep 1
echo 42
sleep 1
echo done # signal end of stdout
echo done 1>&2 # signal end of stderr
stmts

done = %r/^\s*done\s*$/o

thread =
Thread.new do
i,o,e = (mode =~ /open3/io ? Open3::popen3(‘sh’) : Spawn::spawn(‘sh’))

program.each{|stmt| i.puts stmt}
i.flush

rfds = [o,e]

loop do
  break if rfds.empty?
  rs, = select rfds, nil, nil
  rs.each do |r|
    rfds.delete(r) and next if r.eof?
    line = ''
    line << r.getc while r.ready?
    rfds.delete(r) and line[done]='' if line =~ done
    next if line.empty?
    printf "%s @ %s: %s\n", (r == o ? 'o' : 'e'), Time.now.to_f, line.inspect
  end
end

end

thread.join

-a

===============================================================================

EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
PHONE :: 303.497.6469
ADDRESS :: E/GC2 325 Broadway, Boulder, CO 80305-3328
URL :: Solar-Terrestrial Physics Data | NCEI
TRY :: for l in ruby perl;do $l -e “print "\x3a\x2d\x29\x0a"”;done
===============================================================================

In article Pine.LNX.4.44.0403250804470.6994-100000@fattire.ngdc.noaa.gov,
“Ara.T.Howard” ahoward@fattire.ngdc.noaa.gov writes:

program.each{|stmt| i.puts stmt}
i.flush

On Linux, the script blocks at puts.

Try puts in a thread as:

Thread.new {
program.each{|stmt| i.puts stmt}
i.flush
}

Note that your test script works well on Solaris unlike Linux.

It seems that select(2) behaves differently on Linux and Solaris.
(Since your script uses thread, ruby calls select before a IO
operation implicitly.)

% cat tst
#!/usr/bin/env ruby

i = IO.popen(<<‘End’, ‘w’)
ruby -e ‘STDIN.each {|line| sleep 10 }’
End

def time(tag)
t1 = Time.now
yield
t2 = Time.now
printf “%s: %f\n”, tag, t2 - t1
end

6.times {
time(:select) { IO.select(nil, [i]) }
time(:puts) { i.puts “s” }
}
time(:flush) { i.flush }

% ruby -v tst
ruby 1.9.0 (2004-03-25) [i686-linux]
select: 0.000042
puts: 0.000031
select: 0.028008
puts: 0.000035
select: 9.990686
puts: 0.000029
select: 9.999802
puts: 0.000029
select: 9.999702
puts: 0.000021
select: 9.999867
puts: 0.000028
flush: 0.000007

% ./ruby -v tst
ruby 1.9.0 (2004-03-25) [sparc-solaris2.8]
select: 0.000161
puts: 0.000074
select: 0.000043
puts: 0.000043
select: 0.000039
puts: 0.000043
select: 0.000066
puts: 0.000043
select: 0.000040
puts: 0.000042
select: 0.000039
puts: 0.000042
flush: 0.000017

On Linux, select waits something - read operation on the other end of the pipe?

···


Tanaka Akira

In article Pine.LNX.4.44.0403250804470.6994-100000@fattire.ngdc.noaa.gov,
“Ara.T.Howard” ahoward@fattire.ngdc.noaa.gov writes:

program.each{|stmt| i.puts stmt}
i.flush

On Linux, the script blocks at puts.

Try puts in a thread as:

Thread.new {
program.each{|stmt| i.puts stmt}
i.flush
}

i keen observation - with that change the script runs fine even using open3.
the question is:

why does #puts cause the process to block in a thread which has forked
(open3) vs one that has not (spawn)??

Note that your test script works well on Solaris unlike Linux.

:wink: i’m not suprised…

It seems that select(2) behaves differently on Linux and Solaris. (Since
your script uses thread, ruby calls select before a IO operation
implicitly.)

On Linux, select waits something - read operation on the other end of the pipe?

yes - it does seem so. i have not looked into this however… perhaps i
should check on comp.unix.programmer…

-a

···

On Fri, 26 Mar 2004, Tanaka Akira wrote:

===============================================================================

EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
PHONE :: 303.497.6469
ADDRESS :: E/GC2 325 Broadway, Boulder, CO 80305-3328
URL :: Solar-Terrestrial Physics Data | NCEI
TRY :: for l in ruby perl;do $l -e “print "\x3a\x2d\x29\x0a"”;done
===============================================================================