[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Re: Why does this code deadlock?

Yukihiro Matsumoto

4/18/2007 7:53:00 AM

Hi,

In message "Re: Why does this code deadlock?"
on Wed, 18 Apr 2007 16:21:48 +0900, Nobuyoshi Nakada <nobu@ruby-lang.org> writes:

|At Wed, 18 Apr 2007 08:34:03 +0900,
|Bill Kelly wrote in [ruby-talk:248300]:
|> require 'net/ftp'
|> Thread.abort_on_exception = true
|> ftp = Net::FTP.new('ftp.idsoftware.com')
| th = Thread.new {$SAFE=4; ftp.login('anonymous', 'abc@def.com')}
| Thread.critical = false
| p th.value

Who set Thread.critical? It should be fixed, I think.

matz.

4 Answers

Bill Kelly

4/18/2007 9:03:00 AM

0


From: "Yukihiro Matsumoto" <matz@ruby-lang.org>
>
> In message "Re: Why does this code deadlock?"
> on Wed, 18 Apr 2007 16:21:48 +0900, Nobuyoshi Nakada <nobu@ruby-lang.org> writes:
>
> |At Wed, 18 Apr 2007 08:34:03 +0900,
> |Bill Kelly wrote in [ruby-talk:248300]:
> |> require 'net/ftp'
> |> Thread.abort_on_exception = true
> |> ftp = Net::FTP.new('ftp.idsoftware.com')
> | th = Thread.new {$SAFE=4; ftp.login('anonymous', 'abc@def.com')}
> | Thread.critical = false
> | p th.value
>
> Who set Thread.critical? It should be fixed, I think.

It seems to be something called by ftp.login: (I added some
sleeps to coordinate the two threads.)

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
require 'fastthread'
require 'net/ftp'
Thread.abort_on_exception = true
puts "1. crit=#{Thread.critical}"
ftp = Net::FTP.new('ftp.idsoftware.com')
puts "2. crit=#{Thread.critical}"
th = Thread.new {sleep(2); $SAFE=4; sleep(2); ftp.login('anonymous', 'abc@def.com')}
puts "3. crit=#{Thread.critical}"
sleep(3)
puts "4. crit=#{Thread.critical}"
sleep(2)
puts "5. crit=#{Thread.critical}"
# Thread.critical = false
p th.value
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

$ ruby -v ftp_safe_test.rb
ruby 1.8.6 (2007-03-16 patchlevel 3) [i686-linux]
1. crit=false
2. crit=false
3. crit=false
4. crit=false
5. crit=true
ftp_safe_test.rb:14:in `value': Thread(0x525ad708): deadlock (fatal)
from ftp_safe_test.rb:14


If I uncomment the `Thread.critical = false', it prints:

$ ruby -v ftp_safe_test.rb
ruby 1.8.6 (2007-03-16 patchlevel 3) [i686-linux]
1. crit=false
2. crit=false
3. crit=false
4. crit=false
5. crit=true
/opt/lib/ruby/1.8/monitor.rb:284:in `mon_acquire': Insecure: can't modify instance variable (SecurityError)
from ftp_safe_test.rb:14:in `value'
from ftp_safe_test.rb:14


This is in MonitorMixin::ConditionVariable:

def mon_acquire(queue)
while @mon_owner && @mon_owner != Thread.current
queue.push(Thread.current)
Thread.stop
Thread.critical = true
end
@mon_owner = Thread.current
end


It looks like several methods in that class may want
ensure blocks enforcing their final `Thread.critical = false'
statements? Such as mon_try_enter, mon_enter, mon_exit,
etc.


Regards,

Bill



Leslie Viljoen

4/18/2007 8:16:00 PM

0

On 4/18/07, Bill Kelly <billk@cts.com> wrote:
>
> From: "Yukihiro Matsumoto" <matz@ruby-lang.org>
> >
> > In message "Re: Why does this code deadlock?"
> > on Wed, 18 Apr 2007 16:21:48 +0900, Nobuyoshi Nakada <nobu@ruby-lang.org> writes:
> >
> > |At Wed, 18 Apr 2007 08:34:03 +0900,
> > |Bill Kelly wrote in [ruby-talk:248300]:
> > |> require 'net/ftp'
> > |> Thread.abort_on_exception = true
> > |> ftp = Net::FTP.new('ftp.idsoftware.com')
> > | th = Thread.new {$SAFE=4; ftp.login('anonymous', 'abc@def.com')}
> > | Thread.critical = false
> > | p th.value
> >
> > Who set Thread.critical? It should be fixed, I think.
>
> It seems to be something called by ftp.login: (I added some
> sleeps to coordinate the two threads.)
>
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> require 'fastthread'
> require 'net/ftp'
> Thread.abort_on_exception = true
> puts "1. crit=#{Thread.critical}"
> ftp = Net::FTP.new('ftp.idsoftware.com')
> puts "2. crit=#{Thread.critical}"
> th = Thread.new {sleep(2); $SAFE=4; sleep(2); ftp.login('anonymous', 'abc@def.com')}
> puts "3. crit=#{Thread.critical}"
> sleep(3)
> puts "4. crit=#{Thread.critical}"
> sleep(2)
> puts "5. crit=#{Thread.critical}"
> # Thread.critical = false
> p th.value
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
> $ ruby -v ftp_safe_test.rb
> ruby 1.8.6 (2007-03-16 patchlevel 3) [i686-linux]
> 1. crit=false
> 2. crit=false
> 3. crit=false
> 4. crit=false
> 5. crit=true
> ftp_safe_test.rb:14:in `value': Thread(0x525ad708): deadlock (fatal)
> from ftp_safe_test.rb:14
>
>
> If I uncomment the `Thread.critical = false', it prints:
>
> $ ruby -v ftp_safe_test.rb
> ruby 1.8.6 (2007-03-16 patchlevel 3) [i686-linux]
> 1. crit=false
> 2. crit=false
> 3. crit=false
> 4. crit=false
> 5. crit=true
> /opt/lib/ruby/1.8/monitor.rb:284:in `mon_acquire': Insecure: can't modify instance variable (SecurityError)
> from ftp_safe_test.rb:14:in `value'
> from ftp_safe_test.rb:14
>
>
> This is in MonitorMixin::ConditionVariable:
>
> def mon_acquire(queue)
> while @mon_owner && @mon_owner != Thread.current
> queue.push(Thread.current)
> Thread.stop
> Thread.critical = true
> end
> @mon_owner = Thread.current
> end

ftp.login calls synchronize in thread.rb, which calls lock in
thread.rb, which sets critical:

def lock
while (Thread.critical = true; @locked)
@waiting.push Thread.current
Thread.stop
end
@locked = true
Thread.critical = false
self
end

Maybe I'm barking up the wrong tree.

Bill Kelly

4/18/2007 9:00:00 PM

0


From: "Leslie Viljoen" <leslieviljoen@gmail.com>
>
> ftp.login calls synchronize in thread.rb, which calls lock in
> thread.rb, which sets critical:
>
> def lock
> while (Thread.critical = true; @locked)
> @waiting.push Thread.current
> Thread.stop
> end
> @locked = true
> Thread.critical = false
> self
> end

Hmm. Not shure if this has changed, but in the 1.8.6 sources,
net/ftp uses MonitorMixin:

require "monitor"

module Net
...
class FTP
include MonitorMixin

...
end
end

Tracing the execution with a set_trace_func shows
Net::FTP#login -> MonitorMixin#synchronize -> MonitorMixin#mon_enter
setting Thread.critical:

call /opt/lib/ruby/1.8/net/ftp.rb:371 login Net::FTP
line /opt/lib/ruby/1.8/net/ftp.rb:372 login Net::FTP
line /opt/lib/ruby/1.8/net/ftp.rb:372 login Net::FTP
c-call /opt/lib/ruby/1.8/net/ftp.rb:372 == String
c-return /opt/lib/ruby/1.8/net/ftp.rb:372 == String
c-call /opt/lib/ruby/1.8/net/ftp.rb:372 == String
c-return /opt/lib/ruby/1.8/net/ftp.rb:372 == String
line /opt/lib/ruby/1.8/net/ftp.rb:376 login Net::FTP
line /opt/lib/ruby/1.8/net/ftp.rb:377 login Net::FTP
call /opt/lib/ruby/1.8/monitor.rb:235 synchronize MonitorMixin
line /opt/lib/ruby/1.8/monitor.rb:236 synchronize MonitorMixin
call /opt/lib/ruby/1.8/monitor.rb:209 mon_enter MonitorMixin
line /opt/lib/ruby/1.8/monitor.rb:210 mon_enter MonitorMixin
c-call /opt/lib/ruby/1.8/monitor.rb:210 critical= Thread
c-return /opt/lib/ruby/1.8/monitor.rb:210 critical= Thread
line /opt/lib/ruby/1.8/monitor.rb:211 mon_enter MonitorMixin
call /opt/lib/ruby/1.8/monitor.rb:278 mon_acquire MonitorMixin
line /opt/lib/ruby/1.8/monitor.rb:279 mon_acquire MonitorMixin
line /opt/lib/ruby/1.8/monitor.rb:284 mon_acquire MonitorMixin
c-call /opt/lib/ruby/1.8/monitor.rb:284 current Thread
c-return /opt/lib/ruby/1.8/monitor.rb:284 current Thread
c-call /opt/lib/ruby/1.8/monitor.rb:284 new Class
c-call /opt/lib/ruby/1.8/monitor.rb:284 initialize Exception
c-return /opt/lib/ruby/1.8/monitor.rb:284 initialize Exception
c-return /opt/lib/ruby/1.8/monitor.rb:284 new Class
c-call /opt/lib/ruby/1.8/monitor.rb:284 backtrace Exception
c-return /opt/lib/ruby/1.8/monitor.rb:284 backtrace Exception
c-call /opt/lib/ruby/1.8/monitor.rb:284 set_backtrace Exception
c-return /opt/lib/ruby/1.8/monitor.rb:284 set_backtrace Exception
raise /opt/lib/ruby/1.8/monitor.rb:284 mon_acquire MonitorMixin
return /opt/lib/ruby/1.8/monitor.rb:279 mon_acquire MonitorMixin
return /opt/lib/ruby/1.8/monitor.rb:210 mon_enter MonitorMixin
return /opt/lib/ruby/1.8/monitor.rb:236 synchronize MonitorMixin
return /opt/lib/ruby/1.8/net/ftp.rb:372 login Net::FTP


Regards,

Bill



Nobuyoshi Nakada

4/19/2007 4:20:00 AM

0

Hi,

At Wed, 18 Apr 2007 16:53:25 +0900,
Yukihiro Matsumoto wrote in [ruby-talk:248325]:
> |At Wed, 18 Apr 2007 08:34:03 +0900,
> |Bill Kelly wrote in [ruby-talk:248300]:
> |> require 'net/ftp'
> |> Thread.abort_on_exception = true
> |> ftp = Net::FTP.new('ftp.idsoftware.com')
> | th = Thread.new {$SAFE=4; ftp.login('anonymous', 'abc@def.com')}
> | Thread.critical = false
> | p th.value
>
> Who set Thread.critical? It should be fixed, I think.

mon_acquire called from mon_enter fails because of
SecurityError, and leaves Thread.critical true.


Index: lib/monitor.rb
===================================================================
--- lib/monitor.rb (revision 12191)
+++ lib/monitor.rb (working copy)
@@ -106,12 +106,15 @@ def wait(timeout = nil)
ensure
Thread.critical = true
- if timer && timer.alive?
- Thread.kill(timer)
+ begin
+ if timer && timer.alive?
+ Thread.kill(timer)
+ end
+ if @waiters.include?(Thread.current) # interrupted?
+ @waiters.delete(Thread.current)
+ end
+ @monitor.instance_eval {mon_enter_for_cond(count)}
+ ensure
+ Thread.critical = false
end
- if @waiters.include?(Thread.current) # interrupted?
- @waiters.delete(Thread.current)
- end
- @monitor.instance_eval {mon_enter_for_cond(count)}
- Thread.critical = false
end
end
@@ -211,4 +214,5 @@ def mon_enter
mon_acquire(@mon_entering_queue)
@mon_count += 1
+ ensure
Thread.critical = false
end
@@ -300,6 +304,7 @@ def mon_exit_for_cond
count = @mon_count
@mon_count = 0
- mon_release
return count
+ ensure
+ mon_release
end
end


--
Nobu Nakada