Bug #4681

Timeout.timeout doesn't actually time out?

Added by mathew murphy almost 3 years ago. Updated over 2 years ago.

[ruby-core:36153]
Status:Rejected
Priority:Normal
Assignee:Akira Tanaka
Category:-
Target version:1.9.3
ruby -v:1.9.2p180 Backport:

Description

Documentation for Timeout says "A way of performing a potentially long-running operation in a thread, and terminating its execution if it hasn‘t finished by a fixed amount of time."

This doesn't actually seem to be what it does.

Example code:

#!/usr/bin/ruby

encoding: UTF-8

require 'open3'

require 'timeout'

puts "Time out after 2 seconds. Count them..."

result = Timeout.timeout(2) do

stdin, stdout, stderr = Open3.capture3("sleep 30")

output = stdout.read + "\n" + stderr.read

end

On my systems, this doesn't time out after 2 seconds. Instead, it runs for 30 seconds, and then throws an error saying it took longer than 2 seconds.

History

#1 Updated by Eric Wong almost 3 years ago

mathew murphy meta@pobox.com wrote:

This doesn't actually seem to be what it does.

Example code:

#!/usr/bin/ruby

encoding: UTF-8

require 'open3'
require 'timeout'

puts "Time out after 2 seconds. Count them..."
result = Timeout.timeout(2) do
stdin, stdout, stderr = Open3.capture3("sleep 30")
output = stdout.read + "\n" + stderr.read
end

On my systems, this doesn't time out after 2 seconds. Instead, it runs
for 30 seconds, and then throws an error saying it took longer than 2
seconds.

It's because open3 has an ensure block where it does Thread#join and
that waits forever, so the timeout thread raised to unblock the
main thread, and then it got stuck again inside the ensure block.

diff --git a/lib/open3.rb b/lib/open3.rb
index b65cb19..d335f9f 100644
--- a/lib/open3.rb
+++ b/lib/open3.rb
@@ -207,6 +207,7 @@ module Open3
begin
return yield(result)
ensure
+ p [ :ensure, FILE, LINE ]
parentio.each{|io| io.close unless io.closed?}
wait
thr.join
end
@@ -702,6 +703,7 @@ module Open3
begin
return yield(
result)
ensure
+ p [ :ensure, FILE, LINE ]
parentio.each{|io| io.close unless io.closed?}
wait
thrs.each {|t| t.join }
end

You can work around it by having an extra timeout block (ugly):


puts "Time out after 2 seconds. Count them..."
Timeout.timeout(2) do
result = Timeout.timeout(2) do
stdin, stdout, stderr = Open3.capture3("sleep 30")
output = stdout.read + "\n" + stderr.read
end
end

But IMHO, timeout is a very fragile module and shouldn't be relied on.

--
Eric Wong

#2 Updated by Yui NARUSE almost 3 years ago

  • Status changed from Open to Assigned
  • Assignee set to Akira Tanaka

#3 Updated by Hiroshi Nakamura almost 3 years ago

  • Target version set to 1.9.3

#4 Updated by Motohiro KOSAKI almost 3 years ago

  • Status changed from Assigned to Rejected

I think Eric described the reason and workaround at commetn#1. IOW, it's spec unfortunately.

#5 Updated by mathew murphy over 2 years ago

Just noting for the benefit of anyone else passing by that I posted a way to do popen3 with timeout at
https://gist.github.com/1032297

Also available in: Atom PDF