Thread mystery

any idea why this script slows downs drastically as it runs and seems to
leak
memory?

require ‘sync’

class Switch
ON, OFF, NEITHER = true, false, nil

def initialize state = OFF
extend Sync_m
@state = NEITHER
@observers = []
end

def on!
synchronize(:EX){
warn ‘on!’
@state = ON
notity_observers
}
end

def off!
synchronize(:EX){
warn ‘off!’
@state = OFF
notity_observers
}
end

def notity_observers
synchronize(:SH){
@observers.each do |o|
o.notify @state
end
}
end

def add_observer o
synchronize(:EX){
@observers << o
}
end
end

class SwitchToggle
def initialize switch
@switch = switch
@switch.add_observer self
end
def notify of
case of
when Switch::ON
Thread.new{ @switch.off! }
when Switch::OFF
Thread.new{ @switch.on! }
else
raise of.to_s
end
end
end

switch = Switch.new
toggle = SwitchToggle.new switch

switch.on!

STDIN.gets

-a

[email protected] wrote:

any idea why this script slows downs drastically as it runs and seems to
leak
memory?

No idea, except that it seems to be related to the thread creation
(maybe because it’s within the sync block). The following works around
it, and is probably more efficient anyway. I guess the semantics are
close to the original.

require ‘sync’
require ‘thread’

class Switch
ON, OFF, NEITHER = true, false, nil

def initialize state = OFF
extend Sync_m
@state = NEITHER
@observers = []
end

def on!
synchronize(:EX){
warn ‘on!’
@state = ON
notity_observers
}
end

def off!
synchronize(:EX){
warn ‘off!’
@state = OFF
notity_observers
}
end

def notity_observers
synchronize(:SH){
@observers.each do |o|
o.notify @state
end
}
end

def add_observer o
synchronize(:EX){
@observers << o
}
end
end

class SwitchToggle
def initialize switch
@switch = switch
@switch.add_observer self
@q = Queue.new
Thread.new do
loop {@q.pop.call}
end
end
def notify of
case of
when Switch::ON
later{ @switch.off! }
when Switch::OFF
later{ @switch.on! }
else
raise of.to_s
end
end
def later(&bl)
@q << bl
end
end

switch = Switch.new
toggle = SwitchToggle.new switch

switch.on!

STDIN.gets

[email protected] writes:

any idea why this script slows downs drastically as it runs and seems to leak
memory?

First, I’m not a Ruby expert or something, but…

[Code skipped]

switch = Switch.new

Here you create the switch.

toggle = SwitchToggle.new switch

Here, toggle is created and registered as listener (in the constructor)

switch.on!

So now you switch it on.
Toggle gets notified, and creates a thread that switches it off.
But when that Thread switches it off, toggle gets notified, and starts a
thread that switches it on again…
Basically it’s an infinite loop, and i suspect it’s leaking because GC
doesn’t kick in.

On Jul 5, 2006, at 2:30 PM, [email protected] wrote:

any idea why this script slows downs drastically as it runs and
seems to leak
memory?

[…]

switch.on!

def count_objs
klasses = Hash.new 0
ObjectSpace.each_object do |o| klasses[o.class] += 1 end
return klasses.map { |k,v| “\t#{k}: #{v}” }.join("\n")
end

Thread.start { loop { puts count_objs; sleep 1 } }.join

Running this, then taking two snapshots, one after startup and one
after its really slow:

$ diff -u a b
— a 2006-07-05 15:28:29.000000000 -0700
+++ b 2006-07-05 15:28:12.000000000 -0700
@@ -2,17 +2,17 @@
Object: 3
Bignum: 3
fatal: 1

  •    String: 557
    
  •    Hash: 2
    
  •    Hash: 17
    
  •    String: 842
        Module: 17
        SwitchToggle: 1
        Class: 153
    
  •    Array: 16
    
  •    Array: 414
        SystemStackError: 1
        Float: 5
        Binding: 1
    
  •    Thread: 4
    
  •    Thread: 152
        ThreadGroup: 1
        IO: 3
    
  •    File: 2
    
  •    File: 1
        Switch: 1
    

Why aren’t those threads getting GC’d?


Eric H. - [email protected] - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com

On Jul 5, 2006, at 3:31 PM, Tom R. wrote:

switch = Switch.new
So now you switch it on.
Toggle gets notified, and creates a thread that switches it off.
But when that Thread switches it off, toggle gets notified, and
starts a
thread that switches it on again…
Basically it’s an infinite loop, and i suspect it’s leaking because GC
doesn’t kick in.

No, the number of live threads (Thread.list) remains stable.
Watching ObjectSpace shows the GC operating, but the number of thread
instances never decreases.


Eric H. - [email protected] - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com

On Thu, 6 Jul 2006, Joel VanderWerf wrote:

No idea, except that it seems to be related to the thread creation (maybe
because it’s within the sync block). The following works around it, and is
probably more efficient anyway. I guess the semantics are close to the
original.

very interesting joel. thanks.

i seem to always end up using to Queue with threads these days anyhow -
so i
guess this is fine…

i suppose it makes sense that one shouldn’t create threads from inside a
sync
block since it uses Thread.critical inside and, therefore, must assume
only
only thread is running at times. i wonder if there is a pattern for
safely
creating threads from within another taking into account
Thread.critical=true…

cheers.

-a

On Thu, 6 Jul 2006, Tom R. wrote:

But when that Thread switches it off, toggle gets notified, and starts a
thread that switches it on again…
Basically it’s an infinite loop, and i suspect it’s leaking because GC
doesn’t kick in.

it would be, but the synchronize prevents two threads from entering any
of the
Switch methods at a time so only thread can actually be running at once.

-a

On Thu, 6 Jul 2006, Eric H. wrote:

Why aren’t those threads getting GC’d?

this is pretty suspect

file: sync.rb

245 def sync_initialize
246 @sync_mode = UN
247 @sync_waiting = []
248 @sync_upgrade_waiting = []
249 @sync_sh_locker = Hash.new
250 @sync_ex_locker = nil
251 @sync_ex_count = 0
252 end
253
254 def initialize(*args)
255 sync_initialize
256 super
257 end
258
259 def sync_try_lock_sub(m)
260 case m
261 when SH
262 case sync_mode
263 when UN
264 self.sync_mode = m
265 sync_sh_locker[Thread.current] = 1
266 ret = true

note that unlocking never releases the reference! i’m going to test a
patch
using something like

249 @sync_sh_locker = Hash.new{|h,k| h[k] = 1}
265 sync_sh_locker.delete Thread.current

… more later …

-a

[email protected] wrote:

i seem to always end up using to Queue with threads these days anyhow -
so i
guess this is fine…

Yep. Thread w/o Queue for me would be like Car w/o Gas. Or maybe Car w/
$5/gallon gas.

i suppose it makes sense that one shouldn’t create threads from inside a
sync
block since it uses Thread.critical inside and, therefore, must assume only
only thread is running at times. i wonder if there is a pattern for safely
creating threads from within another taking into account
Thread.critical=true…

In this case, though, Thread.critical==false while in the notify block,
so it can’t be causing this problem, right?

On Thu, 6 Jul 2006, Eric H. wrote:

Why aren’t those threads getting GC’d?

check out my post on ruby-core eric…

-a

On Wed, 5 Jul 2006, Joel VanderWerf wrote:

block since it uses Thread.critical inside and, therefore, must assume only
only thread is running at times. i wonder if there is a pattern for safely
creating threads from within another taking into account
Thread.critical=true…

In this case, though, Thread.critical==false while in the notify block, so it
can’t be causing this problem, right?

yes, it seems so. still, you were definitely on to something - if no
threads
are created it works…

hmm. i’ll have to read sync.rb a bit more - it’s thick.

-a

2006/7/6, [email protected] [email protected]:

So now you switch it on.
Toggle gets notified, and creates a thread that switches it off.
But when that Thread switches it off, toggle gets notified, and starts a
thread that switches it on again…
Basically it’s an infinite loop, and i suspect it’s leaking because GC
doesn’t kick in.

it would be, but the synchronize prevents two threads from entering any of the
Switch methods at a time so only thread can actually be running at once.

Yes, but they are still queued up against the mutex. I completely
agree with Tom’s analysis. Having an Observer trigger the
notification again is definitively a bad idea - at least if events are
not queued up.

Kind regards

robert

On Thu, 6 Jul 2006, [email protected] wrote:

any idea why this script slows downs drastically as it runs and seems to leak
memory?

I’m not familiar with enough with sync to answer this I suspect, esp.
the
memory part, but…

require ‘sync’

class Switch
ON, OFF, NEITHER = true, false, nil

…in here:

def initialize state = OFF
extend Sync_m
@state = NEITHER
@observers = []
end

state is not used. Should that be
@state = state
otherwise the default OFF doesn’t agree with @state being set
to NEITHER.

synchronize(:EX){
  warn 'off!'
  @state = OFF
  notity_observers
}

end

so both on! and off! notify_observers, however many…

def notity_observers
synchronize(:SH){
@observers.each do |o|
o.notify @state
end
}
end

so could you have a thread for each observer?

@switch = switch
end

end
end

switch = Switch.new
toggle = SwitchToggle.new switch

switch.on!

Not sure what happens when the synchronize is called within itself,
because synchronize calls notity_observers which calls @switch.on!
which calls synchronize which calls notity_observers…

Not spend enough time to really grok this though.

STDIN.gets

    Hugh

On Wednesday 05 July 2006 17:30, [email protected] wrote:

any idea why this script slows downs drastically as it runs and seems to
leak memory?

Slow compared to what? Number of calls per second?

Creating threads and synchronizing is computationally expensive. I’m
not
aware of any language where this isn’t true, especially for
synchronization.
In your code, you incur a 28x lag per call by synchronizing with the
sync
library, and another 14x lag lag per call with the thread creation.*

It looks (from a later email) like you found the memory leak.

— SER

(*) N=100000

a=Time.now
N.times { synchronize( :EX ) { k = 2 + 2 } }
b=Time.now
N.times { k = 2 + 2 }
c=Time.now
puts "Synchronize overhead: #{(b-a)/(c-b)}"

S = Struct.new("S", :k)
a=Time.now
N.times { k = Thread.new {} }
b=Time.now
N.times { k = S.new {} }
c=Time.now
puts "Thread overhead: #{(b-a)/(c-b)}"

Provide a sufficiently large value of N to mask the overhead of the loop
itself.

Confidentiality Notice
This e-mail (including any attachments) is intended only for the
recipients named above. It may contain confidential or privileged
information and should not be read, copied or otherwise used by any
other person. If you are not a named recipient, please notify the sender
of that fact and delete the e-mail from your system.

On Thu, 6 Jul 2006, Sean Russell wrote:

On Wednesday 05 July 2006 17:30, [email protected] wrote:

any idea why this script slows downs drastically as it runs and seems to
leak memory?

Slow compared to what? Number of calls per second?

slow compared to how fast it starts - eventually it actually will halt.

Creating threads and synchronizing is computationally expensive. I’m not
aware of any language where this isn’t true, especially for synchronization.
In your code, you incur a 28x lag per call by synchronizing with the sync
library, and another 14x lag lag per call with the thread creation.*

It looks (from a later email) like you found the memory leak.

yes. there is some sort of memory corruption occuring. why this is so,
however, i have no idea…

cheers.

-a

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs