develooper Front page | perl.perl5.porters | Postings from November 2003

Re: dealing with objects cloning under ithreads

Elizabeth Mattijsen
November 14, 2003 02:16
Re: dealing with objects cloning under ithreads
Message ID:
At 01:56 -0800 11/14/03, Stas Bekman wrote:
>Now let's say I have a class Foo [2] which creates objects with C 
>struct guts in it. Then the the user program becomes:
>package main;
>use threads;
>$obj = Foo->new();
>threads->new(sub { })->detach;
>And here I'm lost. How does the user program know that it needs to 
>call Foo::CLONE_OBJ (or whatever the cloning function is) on $obj? 
>You don't expect users to write code to traverse the stash, look for 
>the objects belonging to class Foo and call $obj->CLONE_OBJ on it. I 
>suppose Foo could export function CLONE into the user namespace, so 
>that function will traverse the stash and clone its own objects. But 
>what happens if there are two classes used by the user program and 
>both need cloning?
>perlmod.pod has very little to say about it, but:
>   If you want to CLONE all objects you will need to keep track of them per
>   package. This is simply done using a hash and Scalar::Util::weaken().
>Does it suggest that class Foo need to maintain an internal storage 
>(e.g. hash) of all objects it creates, weakened so not to interfere 
>with normal DESTROY, and have Foo::CLONE explicitly clone all 
>objects in that storage. Like so:
>package Foo;
>use Scalar::Util;
>my %objects = ();
>sub new {
>   # let's say that new_data() returns a C struct pointer
>   my $data = new_data();
>   my $self = bless $data, __PACKAGE__;
>   my $copy = $self;
>   Scalar::Util::weaken($copy);
>   $objects{"$data"} = $copy;
>   return $self;
>sub CLONE {
>   while (my($k, $v) = each %objects) {
>     # replace the guts of the object with new $data
>     # this affects the object in the user program as well
>     $$v = $v->clone_obj;
>   }
>sub DESTROY {
>   my $self = shift;
>   # some normal DESTROY code
>   # ...
>   # cleanup the storage
>   my $data = $$self;
>   delete $packages{"$data"}; # remove from storage
>sub clone_obj {
>   my $self = shift;
>   my $data = $$self;
>   return clone_data($data); # some XS function that does the cloning
>does this seem the way to go? this code is untested.

I would guess that's the way to go.  Personally, I've never used 
CLONE other than to keep track of whether an object had been created 
in the thread, and if not, ignore teh DESTROY.  But that just works 
because I didn't have to perform any extra actions when the object 
was CLONEd.

>Ideally it'd be nice to just have a CLONE method to be invoked on 
>any cloned object. I suppose it wasn't added because of the huge 
>overhead it'll add to an already deadly slow [1] perl_clone to check 
>every scalar whether it can('CLONE')?

I guess that's the reason.  However, I don't see why we couldn't 
write a Thread::Bless or similar module that would change the "bless" 
of an object into a recording of the object, and call a "clone" 
method  on every object blessed that way.

>1) at the moment perl_clone seems to be an almost impractical thing 
>in a real world if you have lots of modules loaded. On an unloaded 
>CPU in the mod_perl 2.0 test suite (About 150 small packages) it 
>takes at least 5 seconds to run a single perl_clone. So to start 
>threaded mpm Apache with 12 interpreters will take about a minute 
>and longer. If you attempt start 120 interpreters you will have to 
>wait at least 10 minutes. Heh.

Hmmm.... echoes....  ;-)  Where did I hear that before...  ;-)

>2) here is an example of a trivial program which will either hang or 
>segfault depending on your malloc implementation:
>package main;
>use threads;
>use GTop;
>$Foo::gtop = GTop->new;
>threads->new(sub { print "thread started\n";})->detach for (1..3);
>here all 3 threads get a copy $Foo::gtop, and then try to destroy it 
>(4 times). Only the first one does it successfully, as gtop returns 
>an object which is a wrapper around a C struct. If you don't have 
>GTop, replace it with some other class which returns a similar 
>object and you will see what the problem is.

Indeed.  But this can be fixed rather easily: you want the DESTROY to 
only destroy in the original thread.  My take on that is:

package Foo;
my $clonelevel = 0;

sub CLONE { $clonelevel++ }

sub new { bless { clonelevel => $clonelevel },shift }

    return if $_[0]->{clonelevel} != $clonelevel; # return if we're 
not in the creating thread
    cleanup otherwise

There are probably other ways of doing that, which you can use if the 
object is not a hash ref, e.g. the refaddress of the object as key in 
a hash (a cloned object has a different refaddress) and look that up. 
Hmmm...  an idea for a Thread::xxx module is brewing...  ;-)

>Oh, the joy of threads.

Nice, huh?   ;-)

Liz Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at | Group listing | About