in reply to Re: socio-political guidance sought
in thread socio-political guidance sought

My apologies for the abbreviated explaination, I was more asking about procedure than code, but since there is interest ;)
The culprit is here:
@@ -395,10 +395,7 @@ if ($realpack) { # we have a blessed ref $out .= ', \'' . $realpack . '\'' . ' )'; - my $toaster = $s->{toaster}; - if (UNIVERSAL::can($val, $toaster)){ - $out .= '->' . $s->{toaster} . '()' if $s->{toaster} ne ''; - } + $out .= '->' . $s->{toaster} . '()' if $s->{toaster} ne ''; $s->{apad} = $blesspad; } $s->{level}--;

Without the patch, if I set up an instance of Data::Dumper as such:
my $dumper = new Data::Dumper(undef,'thaw')-> Freezer('DUMPER_freeze')->Toaster('DUMPER_thaw');
and one or more of the serialized objects is frozen like such:
my $freeze=$dumper->Values($ref_to_some_object)->Dump;
All serialized output will be of the form:
bless(DATA, CLASS)->(DUMPER_thaw);
so when I attempt to reconstitute it (thaw it):
my $thaw; # 'thaw' is the handle that I told DD to use eval $freeze # sets thaw
DD calls the DUMPER_thaw method on the object, whether it had it or not. If it has it, fine, use it. Otherwise, DD should use its default thawing capabilities.
Currenlty it will die with:
WARNING(Freezer method call failed): Can't locate object method "DUMPER_freeze" via package "TestAutoDBOutside_1" at /tools/lib/perl5/5.8.4/i686-linux-thread-multi/Data/Dumper.pm line 158. ... Attempt to free temp codematurely: SV 0x83ee194 at /tools/lib/perl5/5.8.4/i686-linux-thread-multi/Data/Dumper.pm line 501. Scalars leaked: 1

which all look like warnings, but the object never gets thawed.
Here is the test wich I included with the patch that explains things more thoroughly (it should only pass after DD is patched):
package DD_test; use lib qw(../blib/lib ../blib/arch); use Data::Dumper; use Test::More qw/no_plan/; $Data::Dumper::Useperl = 1; my $DUMPER=new Data::Dumper([undef],['thaw'])->Purity(1)->Indent(1)->Freezer('DUMPER_ +freeze')->Toaster('DUMPER_thaw'); my $f = new freezable; my $t = new unthawable; $f->{_OTHER} = new unthawable; $t->{_OTHER} = new freezable; my ($thaw); print "-" x 100, "\n"; print "Freezable\n"; print "-" x 100, "\n"; my $th = $DUMPER->Values([$f])->Dump; #print Dumper $th; eval $th; #sets $thaw #print Dumper $thaw; isa_ok($thaw,'freezable'); is($thaw->oid, 1); isnt($thaw->can('DUMPER_thaw'),undef); is($thaw->{_OTHER}->oid, 3); is($thaw->{_OTHER}->can('DUMPER_thaw'),undef); undef $thaw; print "-" x 100, "\n"; print "Unfreezable\n"; print "-" x 100, "\n"; my $th2 = $DUMPER->Values([$t])->Dump; #sets $thaw #print Dumper $th2; eval $th2; #sets $thaw #print Dumper $thaw; is($thaw->oid, 3); is($thaw->can('DUMPER_thaw'),undef); is($thaw->{_OTHER}->oid, 1); isnt($thaw->{_OTHER}->can('DUMPER_thaw'),undef); ## freezable package package freezable; sub new { my($self)=@_; return bless {}, $self; } sub oid { return 1; } sub DUMPER_freeze { my($self)=@_; print ">>> DUMPER_freeze ",$self->oid,"\n"; $self->{_OID} = $self->oid; $self->{_CLASS} = ref $self; return $self; } sub DUMPER_thaw { my($self)=@_; print "<<< DUMPER_thaw ", $self->oid, "\n"; return $self; } sub oid2object { shift @_ unless ref($_[0])=~/DBI::/; %__PACKAGE__::OID_2_OBJECT=shift @_ if @_; return \%$__PACKAGE__::OID_2_OBJECT; } ## unthawable package (no DUMPER_freeze, DUMPER_thaw method) package unthawable; sub new { my($self)=@_; return bless {_OID=>$self->oid,_CLASS=>$self}, $self; } sub oid { return 3; }

Replies are listed 'Best First'.
Re^3: socio-political guidance sought
by Jenda (Abbot) on Aug 20, 2004 at 22:08 UTC

    I see. The problem is not with the existence of the Freezer method but the Toaster method, now I understand. This actually looks like a real bug to me. I don't think your code is entirelycorrect though. You call the UNIVERSAL::can() even if the Toaster is not specified. I think the whole change needed is the condition on the line that adds the toaster call:

    if ($realpack) { # we have a blessed ref $out .= ', \'' . $realpack . '\'' . ' )'; - $out .= '->' . $s->{toaster} . '()' if $s->{toaster} ne ''; + $out .= '->' . $s->{toaster} . '()' + if $s->{toaster} ne '' and UNIVERSAL::can( $val, $s->{toaster +}); $s->{apad} = $blesspad; }

    Update: The problem is that this tests for the Toaster method while the data is serialized/exported, not while it's deserialized/imported. Which might make a difference. With the original code as soon as the export was created with Toaster set to something you can get "notified" about all restored objects, all you have to do is to create a sub in UNIVERSAL package. With your (or mine) change your method only gets called for objects that had the Toaster method at the time of the export.

    You could change Data::Dumper to generate something like this:

    ... map( (UNIVERSAL::can( $_, 'Toaster') ? $_->Toaster() : $_), bless ( {}, 'Package::Name') ), ...
    but I think the easiest solution is to define
    sub UNIVERSAL::Toaster { $_[0] }
    in your script/module and all classes that do not define their own Toaster will inherit this default one.

    Jenda
    Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.
       -- Rick Osborne

      Thanks for the optimization, I'll use it for the Dumper.pm patch.

      > The problem is that this tests for the Toaster method while the data is serialized/exported, not while it's deserialized/imported.

      This is true, but for my purposes, this is better. Since the serialized string is being inserted into the database, I don't want it to have the thaw method associated with it unless it has a thaw method (justification: its misleading to the user)

      > but I think the easiest solution is to define
      > sub UNIVERSAL::Toaster { $_[0] }

      ahh, but I don't have control over any non-AutoDB objects (I can't inject any methods into their space). Non-AutoDB objects are persisted only because they are pointed to by AutoDB objects (DD happily recurses over all references and builds a structure). But they have no ancestral connection to AutoDB.

      Thus, this method won't work for me :(

        I don't see why. You have to choose a method name that the non-AutoDB objects do not use anyway, otherwise their method gets called during the deserialization and mess things up. If you choose a name that none of the objects use they will not notice you injected a new method into their space.

        Let's assume you want to use the name "_thaw". Your AutoDB objects define their own _thaw() method and do whatever's necessary after the deserialization. And they do it in the way expected by Data::Dumper. Apart from this you have a class that knows nothing about AutoDB or Data::Dumper, but defines a _thaw() method. That method is not supposed to be called after deserialization, it does not return the revived object, it does something you don't really be done at that time. In this case you run into problems no matter when do you check for the _thaw() method's existence. Whether you use your modified Data::Dumper or whether you define a default _thaw(), Data::Dumper calls the object's _thaw() method and thigs get messed up.

        The only case when the behaviour differs is if the _thaw() method is not defined when serializing and is when deserializing. While this may seem like a minor unimportant thing it actually is not. Suppose a class does not need to do anything special upon deserialization, therefore it does not contain _thaw(), you serialize some data structure containing objects of that class and store them in the database. Then a year later you extend the class and it now needs to do something after deserialization so you do define a _thaw() method. Now with your modified Data::Dumper the old exports do not call the _thaw(), new exports do. Things break. With my proposal the _thaw() defined in the class overloads the default from UNIVERSAL and the right _thaw() gets called both for old and new exports.

        You might actually use the _thaw() method not only to reopen database or socked connections and stuff like that, you may also use it to "upgrade" the object. If you change the internal layout of a class or just add some new fields you may want/need to initialize the new fields or convert the deserialized data.

        Jenda
        Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.
           -- Rick Osborne