jaylaw64 has asked for the wisdom of the Perl Monks concerning the following question:

I have a simple of example of using Storable on singleton objects that must have a simple error, but I sure can't find it. I appeal to the wisdom of the monks...

In the example below. The package Number is a silly singleton. I then have one script that creates a structure containing two references to the same singleton object. It is then store'd. Then a second script reads this and prints the object. The second occurence of the singleton is not restored.

package Number; use strict; # Hash to hold singletons use vars qw ($numbers); $numbers = {}; sub new { my ($class, $number, $text) = @_; # Check for and return an existing singleton return $numbers->{$number} if ($numbers->{$number}); # Build the object my $self = {}; bless($self, $class); $self->{'number'} = $number; $self->{'text'} = $text; # Store it as the singleton definition and return $numbers->{$number} = $self; return $numbers->{$number}; } sub asNumber { my ($self) = @_; return $self->{'number'}; } sub asText { my ($self) = @_; return $self->{'text'}; } sub STORABLE_freeze { my ($self, $cloning) = @_; my $num = $self->asNumber(); my $txt = $self->asText(); print "Freeze $num $txt\n"; return "$num$txt"; } sub STORABLE_attach { my ($class, $cloning, $serialized) = @_; my ($num, $txt) = $serialized =~ /(\d+)(\w+)/; my $self = $class->new($num, $txt); print "Attach $num $txt $self\n"; return $self; } 1;

Here is the script which creates the objects and stores them

#!/usr/bin/env dvperl use strict; use Number; use Storable; my $stuff = {'one' => new Number(1, "One"), 'two' => new Number(2, "Two"), 'twotwo' => new Number(2, "Two")}; foreach my $key (keys %$stuff) { my $number = $stuff->{$key}; my $num = $number->asNumber(); my $txt = $number->asText(); print ("Key: ($key)\tNum: ($num)\tTxt: ($txt)\tObject: ($number)\n +"); } store $stuff, 'storefile';

When I run this I get the expected output

Key: (twotwo)	Num: (2)	Txt: (Two)	Object: (Number=HASH(0x1b97c6a0))
Key: (one)	Num: (1)	Txt: (One)	Object: (Number=HASH(0x1b95ff78))
Key: (two)	Num: (2)	Txt: (Two)	Object: (Number=HASH(0x1b97c6a0))
Freeze 2 Two
Freeze 1 One

This has printed the 'one', 'two', and 'twotwo' elements, and you can see that 'two' and 'twotwo' refer to the same address

The following script reads this and prints it

#!/usr/bin/env dvperl use strict; use Number; use Storable; my $stuff; $stuff = retrieve 'storefile'; foreach my $key (keys %$stuff) { my $number = $stuff->{$key}; my $num = $number->asNumber(); my $txt = $number->asText(); print ("Key: ($key)\tNum: ($num)\tTxt: ($txt)\tObject: ($number)\n +"); }

When I run this I get:

Attach 2 Two Number=HASH(0xf3cc200)
Attach 1 One Number=HASH(0xf3cc7d0)
Key: (twotwo)	Num: (2)	Txt: (Two)	Object: (Number=HASH(0xf3cc200))
Key: (one)	Num: (1)	Txt: (One)	Object: (Number=HASH(0xf3cc7d0))
Key: (two)	Num: ()	Txt: ()	Object: (Number=HASH(0xf1c2f78))

This looks good at the beginning where only 2 calls to attach are present, but when the structure is printed the elements are not good in the second reference, and the address is not the same

please help...

Replies are listed 'Best First'.
Re: Basic STORABLE_attach scenario doesn't work
by SuicideJunkie (Vicar) on Feb 22, 2012 at 20:07 UTC

    I'd suggest printing your serialized string before doing the regex (my ($num, $txt) = $serialized =~ /(\d+)(\w+)/;). It looks like the values are not what you think they should be. Checking whether the regex was successfully matched couldn't hurt either.

Re: Basic STORABLE_attach scenario doesn't work
by jaylaw64 (Novice) on Feb 22, 2012 at 20:19 UTC
    You are correct. that line should be:     my ($num, $txt) = ($serialized =~ /(\d+)(\w+)/);

    The regex needs to be in a list context to return the two values.

    Nice catch but it answer if still wrong. The output now looks like

    Attach num: (2) text: (Two) class: (Number) self: (Number=HASH(0x12299f68))
    Attach num: (1) text: (One) class: (Number) self: (Number=HASH(0x1229a538))
    Key: (twotwo)	Num: (2)	Txt: (Two)	Object: (Number=HASH(0x12299f68))
    Key: (one)	Num: (1)	Txt: (One)	Object: (Number=HASH(0x1229a538))
    Key: (two)	Num: ()	Txt: ()	Object: (Number=HASH 0x120b2f78))
    

    The prints in the attach call now show all the values. You can see that the twotwo and one elements are getting the attached structure, but the 'two' is not ... hmmmm

      OK. I learned something today. Which makes it a good day, even if I did waste about 24 hours of my life on this problem.

      The issue is the use of STORABLE_attach instead of STORABLE_thaw.

      The Number object is not truly a Singleton. There are multiple numbers, this class just ensures that if you ask for an object with the same values you get the same object. But there are multiple objects. (In my real application these are LDAP found users, hurray for Net::LDAP).

      So Storable is smart enough to understand the shared references and "do the right thing". The only issues is how to recreate the $numbers hash during thawing in case objects created after Storable returns are added to the set of known numbers.

      If I define STORABLE_thaw instead of STORABLE_attach, it is only called once for each element of the $numbers hash, I just have to put them back during the thaw. Here is the new numbers class. I've added a new method _populate that is used by both the constructor and the thaw routine to fill in the object and the numbers hash

      package Number; use strict; # Hash to hold singletons use vars qw ($numbers); $numbers = {}; sub new { my ($class, $number, $text) = @_; # Check for and return an existing singleton return $numbers->{$number} if ($numbers->{$number}); # Build the object my $self = {}; bless($self, $class); # Fill in the values $self->_populate($number, $text); return $self; } sub _populate { my ($self, $number, $text) = @_; $self->{'number'} = $number; $self->{'text'} = $text; # Store it as the singleton definition and return $numbers->{$number} = $self; return $numbers->{$number}; } sub asNumber { my ($self) = @_; return $self->{'number'}; } sub asText { my ($self) = @_; return $self->{'text'}; } sub STORABLE_freeze { my ($self, $cloning) = @_; my $num = $self->asNumber(); my $txt = $self->asText(); my $numTxt = "$num$txt"; print "Freeze $numTxt\n"; return "$num$txt"; } sub STORABLE_thaw { my ($obj, $cloning, $serialized) = @_; my ($num, $txt) = ($serialized =~ /(\d+)(\w+)/); printf("Thaw num: ($num) text: ($txt) obj: ($obj)\n", $num, $txt, +$obj); $obj->_populate($num, $txt); return; } 1;

      For completeness here is the creator script

      #!/usr/bin/env dvperl use strict; use Number; use Storable; my $stuff = {'one' => new Number(1, "One"), 'two' => new Number(2, "Two"), 'twotwo' => new Number(2, "Two")}; foreach my $key (keys %$stuff) { my $number = $stuff->{$key}; my $num = $number->asNumber(); my $txt = $number->asText(); print ("Key: ($key)\tNum: ($num)\tTxt: ($txt)\tObject: ($number)\n +"); } store $stuff, 'storefile';

      And the reader script

      #!/usr/bin/env dvperl use strict; use Number; use Storable; my $stuff; $stuff = retrieve 'storefile'; foreach my $key (keys %$stuff) { my $number = $stuff->{$key}; my $num = $number->asNumber(); my $txt = $number->asText(); print ("Key: ($key)\tNum: ($num)\tTxt: ($txt)\tObject: ($number)\n +"); }

      The output of creating

      Key: (twotwo)	Num: (2)	Txt: (Two)	Object: (Number=HASH(0x3bca6a0))
      Key: (one)	Num: (1)	Txt: (One)	Object: (Number=HASH(0x3badf78))
      Key: (two)	Num: (2)	Txt: (Two)	Object: (Number=HASH(0x3bca6a0))
      Freeze 2Two
      Freeze 1One
      

      Note: 'two' and 'twotwo' are the same reference but only one STORABLE_freeze call for 2 was made

      And finally, the output of the reader script

      Thaw num: (2) text: (Two) obj: (Number=HASH(0x1ed62f78))
      Thaw num: (1) text: (One) obj: (Number=HASH(0x1edaab80))
      Key: (twotwo)	Num: (2)	Txt: (Two)	Object: (Number=HASH(0x1ed62f78))
      Key: (one)	Num: (1)	Txt: (One)	Object: (Number=HASH(0x1edaab80))
      Key: (two)	Num: (2)	Txt: (Two)	Object: (Number=HASH(0x1ed62f78))
      

      The lesson is only use STORABLE_attach for literal singleton objects. Now I can complete my transition from using Data::Dump to Storable, and get back to my real job.

      This is my first post to perlmonks and although I eventually figured this one out myself, I am grateful for the many times I have solved a problem by simply tapping into the wisdom of the monks. I leave this detailed explanation in the hopes that it will be found in the future by someone as lost as myself. Despite the power of google I could not find another complete example.