# Thread death callback to be called after fork

**URL:** <https://www.ruby-forum.com/t/thread-death-callback-to-be-called-after-fork/75523>\
**Category:** Ruby\
**Created:** [November 10, 2006, 6:02pm UTC](https://www.ruby-forum.com/t/thread-death-callback-to-be-called-after-fork/75523 "2006-11-10T18:02:54Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![unknown](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/u/4bbf92/32.png) [@unknown](https://www.ruby-forum.com/u/unknown)\
**Post date:** [November 10, 2006, 6:02pm UTC](https://www.ruby-forum.com/t/thread-death-callback-to-be-called-after-fork/75523/1 "2006-11-10T18:02:54Z")

</div>

forking kills threads, if it didn’t, this would hang:

harp:~ \> cat a.rb  
t = Thread.new{ sleep }  
fork{ t.join; exit } and Process.wait

harp:~ \> ruby a.rb

but, of course, it does not

now, say you need to know when a thread has been killed due to forking,  
it  
appears there is no message sent to the thread:

harp:~ \> cat a.rb  
t = Thread.new{ sleep }  
class \<\< t  
instance\_methods.each{|m| undef\_method m unless m[/\_\_/]}  
end  
fork{ exit } and Process.wait

harp:~ \> ruby a.rb  
a.rb:4: warning: undefining ` __send__' may cause serious problem a.rb:4: warning: undefining` **id** ’ may cause serious problem

otherwise we’d see an error thrown for some message being sent to ‘t’.

definining a finalizer is one way to trigger notification of thread  
death on  
fork:

harp:~ \> cat a.rb  
t = Thread.new{ sleep }

ObjectSpace.define\_finalizer(t){ puts “t death in #{ $$ }” }

fork{ exit } and Process.wait

harp:~ \> ruby a.rb  
t death in 6671  
t death in 6670

but, for this to work you must inherit **every** exit handler from the  
parent:

```
 harp:~ > cat a.rb
 t = Thread.new{ sleep }

 ObjectSpace.define_finalizer(t){ puts "t death in #{ $$ }" }

 at_exit{ puts "parent(#{ $$ }) death" }

 fork{
   at_exit{ exit! } # don't inherit at_exit 

```

handlers from parent!!!  
at\_exit{ puts “child(#{ $$ }) death” }  
exit  
} and Process.wait

```
 harp:~ > ruby a.rb
 child(6677) death
 parent(6676) death
 t death in 6676

```

as you see above, if we don’t the finalizer is called only in the  
parent.

now, in my case i have a thread that’s reading from a socket in the  
background, when this thread dies and death trigger is fired. this  
works  
great. this issue is that any fork in the code will duplicate the  
socket  
being read from in the child, and i don’t want that. my thinking is  
that i  
could handle this by trapping the finalization of the thread doing the  
reading  
and, when it’s finalized, shutdown the socket. eg

t = Thread.new{ socket.read; death\_callback.call }

ObjectSpace.define\_finalizer(t){ socket.close }

fork{  
# socket should get close here since t gets killed  
exit  
}

this issue is that i cannot inherit all at\_exit handlers from the  
parent, eg  
my code is closer to

t = Thread.new{ socket.read; death\_callback.call }

ObjectSpace.define\_finalizer(t){ socket.close }

fork{  
at\_exit{ exit! } # don’t inherit from parent  
# socket doesn’t get closed here, even though t  
gets reaped  
exit  
}

so i was hoping that some message would be sent to ‘t’ in the child,  
‘kill’  
for instance, the shut it down. however, this doesn’t seem to be the  
case, it  
seems like the thread is killed without any message being sent to it, so  
that  
i cannot trigger an event on it’s death.

is this true? any workarounds?

-a

---

<div class="post-metadata">

**Author:** ![unknown](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/u/4bbf92/32.png) [@unknown](https://www.ruby-forum.com/u/unknown)\
**Post date:** [November 10, 2006, 8:15pm UTC](https://www.ruby-forum.com/t/thread-death-callback-to-be-called-after-fork/75523/2 "2006-11-10T20:15:18Z")

</div>

On 11/10/06, removed\_email\_address@domain.invalid [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> so i was hoping that some message would be sent to ‘t’ in the child, ‘kill’  
> for instance, the shut it down. however, this doesn’t seem to be the case, it  
> seems like the thread is killed without any message being sent to it, so that  
> i cannot trigger an event on it’s death.
> 
> is this true? any workarounds?

I did this quick experiment to see exactly what was going on. Create  
the thread, fork a child, and then print out a message in the child  
and in the parent.

$ cat tmp.rb

t = Thread.new{ sleep }

ObjectSpace.define\_finalizer(t) {puts “t death in #{$$}”}  
at\_exit {puts “parent(#{$$}) death”}

fork {  
at\_exit {puts “child(#{$$}) death”}  
puts “thread status ‘#{t.status}’”  
puts “sleeping in child”  
sleep 2  
exit  
} and Process.wait

puts “sleeping in parent”  
sleep 2

puts “DONE”

$ ruby tmp.rb  
thread status ‘false’  
sleeping in child  
child(29926) death  
parent(29926) death  
t death in 29926  
sleeping in parent  
DONE  
parent(29925) death  
t death in 29925

We see the parent death twice because the child ran the parent’s  
finalizers – that is also why we see the thread death in the child.  
However, the status of the thread on entering is “false”. This means  
that the thread has already terminated, but its finalizer does not get  
called in the child until the child exits.

Ara, is your desire to close the socket at the beginning of the child  
process or at the end?

Either way the workaround is the same. Define the finalizer as a Proc  
object, and pass this Proc to the child. The child will call the Proc  
either at the beginning of the fork or at the end using its own  
at\_exit call …

$ cat tmp2.rb

t = Thread.new{ sleep }

finalizer = lambda {puts “t death in #{$$}”}  
ObjectSpace.define\_finalizer(t, finalizer)  
at\_exit {puts “parent(#{$$}) death”}

fork {  
at\_exit {exit!}  
at\_exit {puts “child(#{$$}) death”}  
at\_exit {finalizer.call}  
puts “thread status ‘#{t.status}’”  
puts “sleeping in child”  
sleep 2  
exit  
} and Process.wait

puts “sleeping in parent”  
sleep 2

puts “DONE”

$ ruby tmp2.rb  
thread status ‘false’  
sleeping in child  
t death in 29967  
child(29967) death  
sleeping in parent  
DONE  
parent(29966) death  
t death in 29966

Now, if you want to kill the socket at the start of the fork, then  
just invoke the finalizer first.

$ cat tmp3.rb

t = Thread.new{ sleep }

finalizer = lambda {puts “t death in #{$$}”}  
ObjectSpace.define\_finalizer(t, finalizer)  
at\_exit {puts “parent(#{$$}) death”}

fork {  
at\_exit {exit!}  
at\_exit {puts “child(#{$$}) death”}  
finalizer.call  
puts “thread status ‘#{t.status}’”  
puts “sleeping in child”  
sleep 2  
exit  
} and Process.wait

puts “sleeping in parent”  
sleep 2

puts “DONE”

$ ruby tmp3.rb  
t death in 29984  
thread status ‘false’  
sleeping in child  
child(29984) death  
sleeping in parent  
DONE  
parent(29983) death  
t death in 29983

Hope this makes sense.

TwP

---

<div class="post-metadata">

**Author:** ![unknown](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/u/4bbf92/32.png) [@unknown](https://www.ruby-forum.com/u/unknown)\
**Post date:** [November 10, 2006, 10:47pm UTC](https://www.ruby-forum.com/t/thread-death-callback-to-be-called-after-fork/75523/3 "2006-11-10T22:47:40Z")

</div>

removed\_email\_address@domain.invalid wrote:  
…

> now, say you need to know when a thread has been killed due to forking, it  
> appears there is no message sent to the thread:

Notification would also be useful in case the thread is holding some  
resource, such as a mutex, when it is killed in the child (as in [1]).

Finalizers don’t seem right–you have to manage them separately from the  
normal code to close resources, which is typically in ensure clauses.

Maybe fork should, in the child process, keep the other threads alive  
long enough to raise an exception in them, so that ensure clauses are  
executed? This would handle all kinds of resources, not just sockets and  
mutexes.

An ensure clause could also keep the thread alive. I’m not sure if this  
is a bug or a feature, but it sounds right. You might want some of the  
threads to survive into the fork.

There probably isn’t a pure ruby way to hack this in, because the  
threads (other than the running thread) are already dead in the fork as  
soon as the fork starts executing ruby code.

[1] [http://wiki.rubygarden.org/Ruby/page/show/ForkableMutex](http://wiki.rubygarden.org/Ruby/page/show/ForkableMutex)
