thread death callback to be called after fork

Discussion in 'Ruby' started by ara.t.howard@noaa.gov, Nov 10, 2006.

  1. Guest

    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
    --
    my religion is very simple. my religion is kindness. -- the dalai lama
     
    , Nov 10, 2006
    #1
    1. Advertising

  2. Tim Pease Guest

    On 11/10/06, <> 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
     
    Tim Pease, Nov 10, 2006
    #2
    1. Advertising

  3. 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

    --
    vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407
     
    Joel VanderWerf, Nov 10, 2006
    #3
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Markus Franz
    Replies:
    2
    Views:
    274
    Terry Reedy
    Dec 24, 2004
  2. Markus Franz
    Replies:
    2
    Views:
    354
    Denis S. Otkidach
    Dec 28, 2004
  3. Hoss Spence
    Replies:
    8
    Views:
    1,011
    Hoss Spence
    Jun 18, 2007
  4. Eric Snow

    os.fork and pty.fork

    Eric Snow, Jan 8, 2009, in forum: Python
    Replies:
    0
    Views:
    574
    Eric Snow
    Jan 8, 2009
  5. asim malik
    Replies:
    1
    Views:
    235
    Martijn Lievaart
    Sep 13, 2009
Loading...

Share This Page