Thead.parent, revisited. Or: Building a call stack


#1

In order to build a diagram of all method calls in an
application, I created a set_trace_func function. This function
builds a call stack. Each thread in each process should have
its own call stack, initialized to the call stack of the parent
process or the parent thread.

The call stacks are stored in the array stack_per_thread. The
initial call stack is an empty array:

stack_per_thread = []
stack_per_thread[[Process.pid, Thread.current]] = []

Creating a new call stack after a fork (both process fork and
thread fork) is done like this:

stack = (stack_per_thread[[Process.pid, Thread.current]] ||=
stack.dup)

Process forks are not a problem, since the last call stack
before the process fork is the one with which the child process
has to continue and does continue.

However, threads are a bit of a problem. Threads don’t have a
parent, which means that it’s not possible to clone the call
stack of the parent thread. I currently fake the concept
“parent thread” by assuming that the last running thread is the
parent thread.

If Parent.thread existed, everything would be perfectly fine:

stack = (stack_per_thread[[Process.pid, Thread.current]] ||=
stack_per_thread[[Process.pid, Thread.parent]].dup)

I observed a couple of traces and noticed, empirically, that a
new thread always gets a couple of CPU cycles (and hence calls
to set_trace_func). If this is always true, my assumption (“the
last thread is the parent thread”) is no problem. If the parent
process can spawn many threads before any of them gets some CPU
cycles, well, then we do have a problem… :{

Ideas? Related work? Anybody able to understand the C code?
Matz?.. :}

gegroet,
Erik V. - http://www.erikveen.dds.nl/


#2

On Tue, 6 Mar 2007, Erik V. wrote:

stack_per_thread[[Process.pid, Thread.current]] = []

I observed a couple of traces and noticed, empirically, that a
new thread always gets a couple of CPU cycles (and hence calls
to set_trace_func). If this is always true, my assumption (“the
last thread is the parent thread”) is no problem. If the parent
process can spawn many threads before any of them gets some CPU
cycles, well, then we do have a problem… :{

Ideas? Related work? Anybody able to understand the C code?
Matz?.. :}

class Thread
class << self
alias_method “new”, “new”
def new *a, &b
child ‘new’, *a, &b
end
alias_method “start”, “start”
def start *a, &b
child ‘start’, *a, &b
end
private
def child as, *a, &b
parent = Thread.current
send(as, *a) do |*a|
Thread.current.parent = parent
b.call *a
end
end
end
def parent
self[‘parent’]
end
def parent= parent
self[‘parent’] = parent
end
def ancestors
return self[‘ancestors’] if self[‘ancestors’]
list = [t = self]
while((t = t.parent))
list << t
end
self[‘ancestors’] = list
end
end

-a


#3

Yep, I know. That’s a “work around”. But, as we’ve discussed
before [1], it’s not water tight.

“Thread#start doesn’t reuse Thread.new. Neither does
rb_thread_create(), which is used in e.g. TK.”

Thanks.

gegroet,
Erik V. - http://www.erikveen.dds.nl/

[1] http://tinyurl.com/38823g


#4

On Tue, 6 Mar 2007, Erik V. wrote:

Yep, I know. That’s a “work around”. But, as we’ve discussed
before [1], it’s not water tight.

“Thread#start doesn’t reuse Thread.new. Neither does
rb_thread_create(), which is used in e.g. TK.”

oh right. forgot.

check out ThreadGroup - i think it’s properties may allow someone to
hack what
you’re after. just looking at that today - but out of time now…

-a