Custom Storable hooks for dclone-ing a light-weight object referencing a heavy-weight object












2














Say I have a tiny object that has a reference to a huge object:



package Tiny;

sub new {
my ($class, $tiny, $large) = @_;
return bless { tiny => $tiny, large => $large };
}


I'd like to create a STORABLE_freeze/STORABLE_thaw pair that lets me (recursively) clone $tiny but maintain/keep the reference to $large as-is without cloning $large too.



I tried temporarily deleting $self->{large} (see below), and putting it in a hash with a Scalar::Util::refaddr key and a weak reference to $large, serializing the rest of $self, and then putting the (weak) reference back into both the original object immediately and the cloned one in STORABLE_thaw, but it is a mess, and on every clone, the weak ref value gets deleted when it goes out of scope, but the key remains in the hash forever leaking memory and I need a global class member hash (%largeWeakRefs) to hold the temporary $large reference. Has a smell.



How it that possible to do this in a cleaner way?



Here is my solution using the hash to hold the large ref temporarily:



package Tiny;

use Scalar::Util qw(refaddr weaken);

sub new {
my ( $class, $tiny, $large ) = @_;
return bless { tiny => $tiny, large => $large }, $class;
}

# Ugly temporary storage to hold $large refs from _freeze to _thaw...
my %largeWeakRefs;
sub STORABLE_freeze {
my ( $self, $cloning ) = @_;
my $large = delete local $self->{large};
my $refaddr = refaddr $large;
$largeWeakRefs{$refaddr} = $large;
weaken $largeWeakRefs{$refaddr};
my %restOfSelf = %$self;
$self->{large} = $large;
return $refaddr, %restOfSelf;
}

sub STORABLE_thaw {
my ($self, $cloning, $refaddr, $restOfSelf) = @_;
%$self = %$restOfSelf;
$self->{large} = $largeWeakRefs{$refaddr};
return $self;
}


(Yes I know, my example only handles cloning, not straight-up freeze and thaw)










share|improve this question



























    2














    Say I have a tiny object that has a reference to a huge object:



    package Tiny;

    sub new {
    my ($class, $tiny, $large) = @_;
    return bless { tiny => $tiny, large => $large };
    }


    I'd like to create a STORABLE_freeze/STORABLE_thaw pair that lets me (recursively) clone $tiny but maintain/keep the reference to $large as-is without cloning $large too.



    I tried temporarily deleting $self->{large} (see below), and putting it in a hash with a Scalar::Util::refaddr key and a weak reference to $large, serializing the rest of $self, and then putting the (weak) reference back into both the original object immediately and the cloned one in STORABLE_thaw, but it is a mess, and on every clone, the weak ref value gets deleted when it goes out of scope, but the key remains in the hash forever leaking memory and I need a global class member hash (%largeWeakRefs) to hold the temporary $large reference. Has a smell.



    How it that possible to do this in a cleaner way?



    Here is my solution using the hash to hold the large ref temporarily:



    package Tiny;

    use Scalar::Util qw(refaddr weaken);

    sub new {
    my ( $class, $tiny, $large ) = @_;
    return bless { tiny => $tiny, large => $large }, $class;
    }

    # Ugly temporary storage to hold $large refs from _freeze to _thaw...
    my %largeWeakRefs;
    sub STORABLE_freeze {
    my ( $self, $cloning ) = @_;
    my $large = delete local $self->{large};
    my $refaddr = refaddr $large;
    $largeWeakRefs{$refaddr} = $large;
    weaken $largeWeakRefs{$refaddr};
    my %restOfSelf = %$self;
    $self->{large} = $large;
    return $refaddr, %restOfSelf;
    }

    sub STORABLE_thaw {
    my ($self, $cloning, $refaddr, $restOfSelf) = @_;
    %$self = %$restOfSelf;
    $self->{large} = $largeWeakRefs{$refaddr};
    return $self;
    }


    (Yes I know, my example only handles cloning, not straight-up freeze and thaw)










    share|improve this question

























      2












      2








      2


      1





      Say I have a tiny object that has a reference to a huge object:



      package Tiny;

      sub new {
      my ($class, $tiny, $large) = @_;
      return bless { tiny => $tiny, large => $large };
      }


      I'd like to create a STORABLE_freeze/STORABLE_thaw pair that lets me (recursively) clone $tiny but maintain/keep the reference to $large as-is without cloning $large too.



      I tried temporarily deleting $self->{large} (see below), and putting it in a hash with a Scalar::Util::refaddr key and a weak reference to $large, serializing the rest of $self, and then putting the (weak) reference back into both the original object immediately and the cloned one in STORABLE_thaw, but it is a mess, and on every clone, the weak ref value gets deleted when it goes out of scope, but the key remains in the hash forever leaking memory and I need a global class member hash (%largeWeakRefs) to hold the temporary $large reference. Has a smell.



      How it that possible to do this in a cleaner way?



      Here is my solution using the hash to hold the large ref temporarily:



      package Tiny;

      use Scalar::Util qw(refaddr weaken);

      sub new {
      my ( $class, $tiny, $large ) = @_;
      return bless { tiny => $tiny, large => $large }, $class;
      }

      # Ugly temporary storage to hold $large refs from _freeze to _thaw...
      my %largeWeakRefs;
      sub STORABLE_freeze {
      my ( $self, $cloning ) = @_;
      my $large = delete local $self->{large};
      my $refaddr = refaddr $large;
      $largeWeakRefs{$refaddr} = $large;
      weaken $largeWeakRefs{$refaddr};
      my %restOfSelf = %$self;
      $self->{large} = $large;
      return $refaddr, %restOfSelf;
      }

      sub STORABLE_thaw {
      my ($self, $cloning, $refaddr, $restOfSelf) = @_;
      %$self = %$restOfSelf;
      $self->{large} = $largeWeakRefs{$refaddr};
      return $self;
      }


      (Yes I know, my example only handles cloning, not straight-up freeze and thaw)










      share|improve this question













      Say I have a tiny object that has a reference to a huge object:



      package Tiny;

      sub new {
      my ($class, $tiny, $large) = @_;
      return bless { tiny => $tiny, large => $large };
      }


      I'd like to create a STORABLE_freeze/STORABLE_thaw pair that lets me (recursively) clone $tiny but maintain/keep the reference to $large as-is without cloning $large too.



      I tried temporarily deleting $self->{large} (see below), and putting it in a hash with a Scalar::Util::refaddr key and a weak reference to $large, serializing the rest of $self, and then putting the (weak) reference back into both the original object immediately and the cloned one in STORABLE_thaw, but it is a mess, and on every clone, the weak ref value gets deleted when it goes out of scope, but the key remains in the hash forever leaking memory and I need a global class member hash (%largeWeakRefs) to hold the temporary $large reference. Has a smell.



      How it that possible to do this in a cleaner way?



      Here is my solution using the hash to hold the large ref temporarily:



      package Tiny;

      use Scalar::Util qw(refaddr weaken);

      sub new {
      my ( $class, $tiny, $large ) = @_;
      return bless { tiny => $tiny, large => $large }, $class;
      }

      # Ugly temporary storage to hold $large refs from _freeze to _thaw...
      my %largeWeakRefs;
      sub STORABLE_freeze {
      my ( $self, $cloning ) = @_;
      my $large = delete local $self->{large};
      my $refaddr = refaddr $large;
      $largeWeakRefs{$refaddr} = $large;
      weaken $largeWeakRefs{$refaddr};
      my %restOfSelf = %$self;
      $self->{large} = $large;
      return $refaddr, %restOfSelf;
      }

      sub STORABLE_thaw {
      my ($self, $cloning, $refaddr, $restOfSelf) = @_;
      %$self = %$restOfSelf;
      $self->{large} = $largeWeakRefs{$refaddr};
      return $self;
      }


      (Yes I know, my example only handles cloning, not straight-up freeze and thaw)







      perl clone






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 12 at 15:40









      Peter V. Mørch

      4,84013150




      4,84013150
























          1 Answer
          1






          active

          oldest

          votes


















          2














          You could add reference counts.



          my %larges;

          sub STORABLE_freeze {
          my ( $self, $cloning ) = @_;
          if ($cloning) {
          my $large_key = pack('j', refaddr(self->{large}));
          $larges{$large_key} //= [ $self->{large}, 0 ];
          ++$larges{$large_key}[1];
          return ( $large_key, $self->{tiny} );
          } else {
          return ( "", $self->{tiny}, $self->{large} );
          }
          }

          sub STORABLE_thaw {
          my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
          if ($cloning) {
          my $large_key = $serialized;
          $self->{ tiny } = shift;
          $self->{ large } = $larges{$large_key}[0];
          --$larges{$large_key}[1]
          or delete($larges{$large_key});
          } else {
          $self->{ tiny } = shift;
          $self->{ large } = shift;
          }
          }


          Untested.



          If the cloning process dies, you'll have a memory leak.





          Alternatively, you could avoid the need for external resources as follows:



          use Inline C => <<'__EOS__';

          IV get_numeric_ref(SV *sv) {
          SvGETMAGIC(sv);
          if (!SvROK(sv))
          croak("Argument not a reference");

          sv = MUTABLE_SV(SvRV(sv));
          SvREFCNT_inc(sv);
          return PTR2IV(sv); /* Despite its name, can be used to convert pointer to IV */
          }

          SV* get_perl_ref_from_numeric_ref(IV iv) {
          SV* sv = PTR2IV(iv);
          return newRV_noinc(sv);
          }

          __EOS__

          sub STORABLE_freeze {
          my ( $self, $cloning ) = @_;
          if ($cloning) {
          return ( pack('j', get_numeric_ref($self->{large})), $self->{tiny} );
          } else {
          return ( "", $self->{tiny}, $self->{large} );
          }
          }

          sub STORABLE_thaw {
          my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
          if ($cloning) {
          $self->{ tiny } = shift;
          $self->{ large } = get_perl_ref_from_numeric_ref(unpack('j', $serialized));
          } else {
          $self->{ tiny } = shift;
          $self->{ large } = shift;
          }
          }


          Didn't test STORABLE_freeze and STORABLE_thaw, but tested the C/XS code using the following:



          use strict;
          use warnings;
          use feature qw( say state );

          use Cpanel::JSON::XS qw( );

          sub _dump {
          state $encoder = Cpanel::JSON::XS->new->canonical->allow_nonref;
          return $encoder->encode($_[0]);
          }

          {
          my %h = ( a => 4, b => 5 );
          say _dump(%h); # {"a":4,"b":5}
          say sprintf "0x%x", %h; # 0x32cdbf8
          say Internals::SvREFCNT(%h); # 1
          my $i = get_numeric_ref(%h);
          say sprintf "0x%x", $i; # 0x32cdbf8
          say Internals::SvREFCNT(%h); # 2
          my $ref = get_perl_ref_from_numeric_ref($i);
          say sprintf "0x%x", $ref; # 0x32cdbf8
          say Internals::SvREFCNT(%h); # 2
          say _dump($ref); # {"a":4,"b":5}
          }


          If the cloning process dies, you'll have a memory leak. I suppose it would be safe to rely on "large" not going anywhere during the cloning process, so you could remove the SvREFCNT_inc and change newRV_noinc to newRV to avoid the potential memory leak.





          To avoid the possible memory leak, never store "large" in the object.



          my %larges;

          sub new {
          my $class = shift;
          my $self = bless({}, $class);
          return $self->_init(@_);
          }

          sub _init {
          my ($self, $tiny, $large) = @_;
          $self->{ tiny } = $tiny;

          {
          my $large_key = pack('j', refaddr($self));
          $self->{ large_key } = $large_key;
          $larges{ $large_key } = $large;
          }

          return $self;
          }

          sub DESTROY {
          my ($self) = @_;
          if (defined( my $large_key = $self->{ large_key } )) {
          delete( $larges{ $large_key } );
          }
          }

          sub STORABLE_freeze {
          my ( $self, $cloning ) = @_;
          if ($cloning) {
          return ( $self->{large_key}, $self->{tiny} );
          } else {
          return ( "", $self->{tiny}, $larges{ $self->{large_key} } );
          }
          }

          sub STORABLE_thaw {
          my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
          if ($cloning) {
          my ($tiny) = @_;
          my $large_key = $serialized;
          $self->_init($tiny, $larges{ $large_key });
          } else {
          $self->_init(@_);
          }
          }


          Untested.



          No memory leaks if the cloning process dies.






          share|improve this answer























          • Thanks, I added reference counts. Great idea!
            – Peter V. Mørch
            Nov 13 at 15:22










          • Fixed a bug in that code
            – ikegami
            Nov 13 at 15:25











          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53265483%2fcustom-storable-hooks-for-dclone-ing-a-light-weight-object-referencing-a-heavy-w%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          2














          You could add reference counts.



          my %larges;

          sub STORABLE_freeze {
          my ( $self, $cloning ) = @_;
          if ($cloning) {
          my $large_key = pack('j', refaddr(self->{large}));
          $larges{$large_key} //= [ $self->{large}, 0 ];
          ++$larges{$large_key}[1];
          return ( $large_key, $self->{tiny} );
          } else {
          return ( "", $self->{tiny}, $self->{large} );
          }
          }

          sub STORABLE_thaw {
          my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
          if ($cloning) {
          my $large_key = $serialized;
          $self->{ tiny } = shift;
          $self->{ large } = $larges{$large_key}[0];
          --$larges{$large_key}[1]
          or delete($larges{$large_key});
          } else {
          $self->{ tiny } = shift;
          $self->{ large } = shift;
          }
          }


          Untested.



          If the cloning process dies, you'll have a memory leak.





          Alternatively, you could avoid the need for external resources as follows:



          use Inline C => <<'__EOS__';

          IV get_numeric_ref(SV *sv) {
          SvGETMAGIC(sv);
          if (!SvROK(sv))
          croak("Argument not a reference");

          sv = MUTABLE_SV(SvRV(sv));
          SvREFCNT_inc(sv);
          return PTR2IV(sv); /* Despite its name, can be used to convert pointer to IV */
          }

          SV* get_perl_ref_from_numeric_ref(IV iv) {
          SV* sv = PTR2IV(iv);
          return newRV_noinc(sv);
          }

          __EOS__

          sub STORABLE_freeze {
          my ( $self, $cloning ) = @_;
          if ($cloning) {
          return ( pack('j', get_numeric_ref($self->{large})), $self->{tiny} );
          } else {
          return ( "", $self->{tiny}, $self->{large} );
          }
          }

          sub STORABLE_thaw {
          my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
          if ($cloning) {
          $self->{ tiny } = shift;
          $self->{ large } = get_perl_ref_from_numeric_ref(unpack('j', $serialized));
          } else {
          $self->{ tiny } = shift;
          $self->{ large } = shift;
          }
          }


          Didn't test STORABLE_freeze and STORABLE_thaw, but tested the C/XS code using the following:



          use strict;
          use warnings;
          use feature qw( say state );

          use Cpanel::JSON::XS qw( );

          sub _dump {
          state $encoder = Cpanel::JSON::XS->new->canonical->allow_nonref;
          return $encoder->encode($_[0]);
          }

          {
          my %h = ( a => 4, b => 5 );
          say _dump(%h); # {"a":4,"b":5}
          say sprintf "0x%x", %h; # 0x32cdbf8
          say Internals::SvREFCNT(%h); # 1
          my $i = get_numeric_ref(%h);
          say sprintf "0x%x", $i; # 0x32cdbf8
          say Internals::SvREFCNT(%h); # 2
          my $ref = get_perl_ref_from_numeric_ref($i);
          say sprintf "0x%x", $ref; # 0x32cdbf8
          say Internals::SvREFCNT(%h); # 2
          say _dump($ref); # {"a":4,"b":5}
          }


          If the cloning process dies, you'll have a memory leak. I suppose it would be safe to rely on "large" not going anywhere during the cloning process, so you could remove the SvREFCNT_inc and change newRV_noinc to newRV to avoid the potential memory leak.





          To avoid the possible memory leak, never store "large" in the object.



          my %larges;

          sub new {
          my $class = shift;
          my $self = bless({}, $class);
          return $self->_init(@_);
          }

          sub _init {
          my ($self, $tiny, $large) = @_;
          $self->{ tiny } = $tiny;

          {
          my $large_key = pack('j', refaddr($self));
          $self->{ large_key } = $large_key;
          $larges{ $large_key } = $large;
          }

          return $self;
          }

          sub DESTROY {
          my ($self) = @_;
          if (defined( my $large_key = $self->{ large_key } )) {
          delete( $larges{ $large_key } );
          }
          }

          sub STORABLE_freeze {
          my ( $self, $cloning ) = @_;
          if ($cloning) {
          return ( $self->{large_key}, $self->{tiny} );
          } else {
          return ( "", $self->{tiny}, $larges{ $self->{large_key} } );
          }
          }

          sub STORABLE_thaw {
          my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
          if ($cloning) {
          my ($tiny) = @_;
          my $large_key = $serialized;
          $self->_init($tiny, $larges{ $large_key });
          } else {
          $self->_init(@_);
          }
          }


          Untested.



          No memory leaks if the cloning process dies.






          share|improve this answer























          • Thanks, I added reference counts. Great idea!
            – Peter V. Mørch
            Nov 13 at 15:22










          • Fixed a bug in that code
            – ikegami
            Nov 13 at 15:25
















          2














          You could add reference counts.



          my %larges;

          sub STORABLE_freeze {
          my ( $self, $cloning ) = @_;
          if ($cloning) {
          my $large_key = pack('j', refaddr(self->{large}));
          $larges{$large_key} //= [ $self->{large}, 0 ];
          ++$larges{$large_key}[1];
          return ( $large_key, $self->{tiny} );
          } else {
          return ( "", $self->{tiny}, $self->{large} );
          }
          }

          sub STORABLE_thaw {
          my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
          if ($cloning) {
          my $large_key = $serialized;
          $self->{ tiny } = shift;
          $self->{ large } = $larges{$large_key}[0];
          --$larges{$large_key}[1]
          or delete($larges{$large_key});
          } else {
          $self->{ tiny } = shift;
          $self->{ large } = shift;
          }
          }


          Untested.



          If the cloning process dies, you'll have a memory leak.





          Alternatively, you could avoid the need for external resources as follows:



          use Inline C => <<'__EOS__';

          IV get_numeric_ref(SV *sv) {
          SvGETMAGIC(sv);
          if (!SvROK(sv))
          croak("Argument not a reference");

          sv = MUTABLE_SV(SvRV(sv));
          SvREFCNT_inc(sv);
          return PTR2IV(sv); /* Despite its name, can be used to convert pointer to IV */
          }

          SV* get_perl_ref_from_numeric_ref(IV iv) {
          SV* sv = PTR2IV(iv);
          return newRV_noinc(sv);
          }

          __EOS__

          sub STORABLE_freeze {
          my ( $self, $cloning ) = @_;
          if ($cloning) {
          return ( pack('j', get_numeric_ref($self->{large})), $self->{tiny} );
          } else {
          return ( "", $self->{tiny}, $self->{large} );
          }
          }

          sub STORABLE_thaw {
          my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
          if ($cloning) {
          $self->{ tiny } = shift;
          $self->{ large } = get_perl_ref_from_numeric_ref(unpack('j', $serialized));
          } else {
          $self->{ tiny } = shift;
          $self->{ large } = shift;
          }
          }


          Didn't test STORABLE_freeze and STORABLE_thaw, but tested the C/XS code using the following:



          use strict;
          use warnings;
          use feature qw( say state );

          use Cpanel::JSON::XS qw( );

          sub _dump {
          state $encoder = Cpanel::JSON::XS->new->canonical->allow_nonref;
          return $encoder->encode($_[0]);
          }

          {
          my %h = ( a => 4, b => 5 );
          say _dump(%h); # {"a":4,"b":5}
          say sprintf "0x%x", %h; # 0x32cdbf8
          say Internals::SvREFCNT(%h); # 1
          my $i = get_numeric_ref(%h);
          say sprintf "0x%x", $i; # 0x32cdbf8
          say Internals::SvREFCNT(%h); # 2
          my $ref = get_perl_ref_from_numeric_ref($i);
          say sprintf "0x%x", $ref; # 0x32cdbf8
          say Internals::SvREFCNT(%h); # 2
          say _dump($ref); # {"a":4,"b":5}
          }


          If the cloning process dies, you'll have a memory leak. I suppose it would be safe to rely on "large" not going anywhere during the cloning process, so you could remove the SvREFCNT_inc and change newRV_noinc to newRV to avoid the potential memory leak.





          To avoid the possible memory leak, never store "large" in the object.



          my %larges;

          sub new {
          my $class = shift;
          my $self = bless({}, $class);
          return $self->_init(@_);
          }

          sub _init {
          my ($self, $tiny, $large) = @_;
          $self->{ tiny } = $tiny;

          {
          my $large_key = pack('j', refaddr($self));
          $self->{ large_key } = $large_key;
          $larges{ $large_key } = $large;
          }

          return $self;
          }

          sub DESTROY {
          my ($self) = @_;
          if (defined( my $large_key = $self->{ large_key } )) {
          delete( $larges{ $large_key } );
          }
          }

          sub STORABLE_freeze {
          my ( $self, $cloning ) = @_;
          if ($cloning) {
          return ( $self->{large_key}, $self->{tiny} );
          } else {
          return ( "", $self->{tiny}, $larges{ $self->{large_key} } );
          }
          }

          sub STORABLE_thaw {
          my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
          if ($cloning) {
          my ($tiny) = @_;
          my $large_key = $serialized;
          $self->_init($tiny, $larges{ $large_key });
          } else {
          $self->_init(@_);
          }
          }


          Untested.



          No memory leaks if the cloning process dies.






          share|improve this answer























          • Thanks, I added reference counts. Great idea!
            – Peter V. Mørch
            Nov 13 at 15:22










          • Fixed a bug in that code
            – ikegami
            Nov 13 at 15:25














          2












          2








          2






          You could add reference counts.



          my %larges;

          sub STORABLE_freeze {
          my ( $self, $cloning ) = @_;
          if ($cloning) {
          my $large_key = pack('j', refaddr(self->{large}));
          $larges{$large_key} //= [ $self->{large}, 0 ];
          ++$larges{$large_key}[1];
          return ( $large_key, $self->{tiny} );
          } else {
          return ( "", $self->{tiny}, $self->{large} );
          }
          }

          sub STORABLE_thaw {
          my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
          if ($cloning) {
          my $large_key = $serialized;
          $self->{ tiny } = shift;
          $self->{ large } = $larges{$large_key}[0];
          --$larges{$large_key}[1]
          or delete($larges{$large_key});
          } else {
          $self->{ tiny } = shift;
          $self->{ large } = shift;
          }
          }


          Untested.



          If the cloning process dies, you'll have a memory leak.





          Alternatively, you could avoid the need for external resources as follows:



          use Inline C => <<'__EOS__';

          IV get_numeric_ref(SV *sv) {
          SvGETMAGIC(sv);
          if (!SvROK(sv))
          croak("Argument not a reference");

          sv = MUTABLE_SV(SvRV(sv));
          SvREFCNT_inc(sv);
          return PTR2IV(sv); /* Despite its name, can be used to convert pointer to IV */
          }

          SV* get_perl_ref_from_numeric_ref(IV iv) {
          SV* sv = PTR2IV(iv);
          return newRV_noinc(sv);
          }

          __EOS__

          sub STORABLE_freeze {
          my ( $self, $cloning ) = @_;
          if ($cloning) {
          return ( pack('j', get_numeric_ref($self->{large})), $self->{tiny} );
          } else {
          return ( "", $self->{tiny}, $self->{large} );
          }
          }

          sub STORABLE_thaw {
          my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
          if ($cloning) {
          $self->{ tiny } = shift;
          $self->{ large } = get_perl_ref_from_numeric_ref(unpack('j', $serialized));
          } else {
          $self->{ tiny } = shift;
          $self->{ large } = shift;
          }
          }


          Didn't test STORABLE_freeze and STORABLE_thaw, but tested the C/XS code using the following:



          use strict;
          use warnings;
          use feature qw( say state );

          use Cpanel::JSON::XS qw( );

          sub _dump {
          state $encoder = Cpanel::JSON::XS->new->canonical->allow_nonref;
          return $encoder->encode($_[0]);
          }

          {
          my %h = ( a => 4, b => 5 );
          say _dump(%h); # {"a":4,"b":5}
          say sprintf "0x%x", %h; # 0x32cdbf8
          say Internals::SvREFCNT(%h); # 1
          my $i = get_numeric_ref(%h);
          say sprintf "0x%x", $i; # 0x32cdbf8
          say Internals::SvREFCNT(%h); # 2
          my $ref = get_perl_ref_from_numeric_ref($i);
          say sprintf "0x%x", $ref; # 0x32cdbf8
          say Internals::SvREFCNT(%h); # 2
          say _dump($ref); # {"a":4,"b":5}
          }


          If the cloning process dies, you'll have a memory leak. I suppose it would be safe to rely on "large" not going anywhere during the cloning process, so you could remove the SvREFCNT_inc and change newRV_noinc to newRV to avoid the potential memory leak.





          To avoid the possible memory leak, never store "large" in the object.



          my %larges;

          sub new {
          my $class = shift;
          my $self = bless({}, $class);
          return $self->_init(@_);
          }

          sub _init {
          my ($self, $tiny, $large) = @_;
          $self->{ tiny } = $tiny;

          {
          my $large_key = pack('j', refaddr($self));
          $self->{ large_key } = $large_key;
          $larges{ $large_key } = $large;
          }

          return $self;
          }

          sub DESTROY {
          my ($self) = @_;
          if (defined( my $large_key = $self->{ large_key } )) {
          delete( $larges{ $large_key } );
          }
          }

          sub STORABLE_freeze {
          my ( $self, $cloning ) = @_;
          if ($cloning) {
          return ( $self->{large_key}, $self->{tiny} );
          } else {
          return ( "", $self->{tiny}, $larges{ $self->{large_key} } );
          }
          }

          sub STORABLE_thaw {
          my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
          if ($cloning) {
          my ($tiny) = @_;
          my $large_key = $serialized;
          $self->_init($tiny, $larges{ $large_key });
          } else {
          $self->_init(@_);
          }
          }


          Untested.



          No memory leaks if the cloning process dies.






          share|improve this answer














          You could add reference counts.



          my %larges;

          sub STORABLE_freeze {
          my ( $self, $cloning ) = @_;
          if ($cloning) {
          my $large_key = pack('j', refaddr(self->{large}));
          $larges{$large_key} //= [ $self->{large}, 0 ];
          ++$larges{$large_key}[1];
          return ( $large_key, $self->{tiny} );
          } else {
          return ( "", $self->{tiny}, $self->{large} );
          }
          }

          sub STORABLE_thaw {
          my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
          if ($cloning) {
          my $large_key = $serialized;
          $self->{ tiny } = shift;
          $self->{ large } = $larges{$large_key}[0];
          --$larges{$large_key}[1]
          or delete($larges{$large_key});
          } else {
          $self->{ tiny } = shift;
          $self->{ large } = shift;
          }
          }


          Untested.



          If the cloning process dies, you'll have a memory leak.





          Alternatively, you could avoid the need for external resources as follows:



          use Inline C => <<'__EOS__';

          IV get_numeric_ref(SV *sv) {
          SvGETMAGIC(sv);
          if (!SvROK(sv))
          croak("Argument not a reference");

          sv = MUTABLE_SV(SvRV(sv));
          SvREFCNT_inc(sv);
          return PTR2IV(sv); /* Despite its name, can be used to convert pointer to IV */
          }

          SV* get_perl_ref_from_numeric_ref(IV iv) {
          SV* sv = PTR2IV(iv);
          return newRV_noinc(sv);
          }

          __EOS__

          sub STORABLE_freeze {
          my ( $self, $cloning ) = @_;
          if ($cloning) {
          return ( pack('j', get_numeric_ref($self->{large})), $self->{tiny} );
          } else {
          return ( "", $self->{tiny}, $self->{large} );
          }
          }

          sub STORABLE_thaw {
          my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
          if ($cloning) {
          $self->{ tiny } = shift;
          $self->{ large } = get_perl_ref_from_numeric_ref(unpack('j', $serialized));
          } else {
          $self->{ tiny } = shift;
          $self->{ large } = shift;
          }
          }


          Didn't test STORABLE_freeze and STORABLE_thaw, but tested the C/XS code using the following:



          use strict;
          use warnings;
          use feature qw( say state );

          use Cpanel::JSON::XS qw( );

          sub _dump {
          state $encoder = Cpanel::JSON::XS->new->canonical->allow_nonref;
          return $encoder->encode($_[0]);
          }

          {
          my %h = ( a => 4, b => 5 );
          say _dump(%h); # {"a":4,"b":5}
          say sprintf "0x%x", %h; # 0x32cdbf8
          say Internals::SvREFCNT(%h); # 1
          my $i = get_numeric_ref(%h);
          say sprintf "0x%x", $i; # 0x32cdbf8
          say Internals::SvREFCNT(%h); # 2
          my $ref = get_perl_ref_from_numeric_ref($i);
          say sprintf "0x%x", $ref; # 0x32cdbf8
          say Internals::SvREFCNT(%h); # 2
          say _dump($ref); # {"a":4,"b":5}
          }


          If the cloning process dies, you'll have a memory leak. I suppose it would be safe to rely on "large" not going anywhere during the cloning process, so you could remove the SvREFCNT_inc and change newRV_noinc to newRV to avoid the potential memory leak.





          To avoid the possible memory leak, never store "large" in the object.



          my %larges;

          sub new {
          my $class = shift;
          my $self = bless({}, $class);
          return $self->_init(@_);
          }

          sub _init {
          my ($self, $tiny, $large) = @_;
          $self->{ tiny } = $tiny;

          {
          my $large_key = pack('j', refaddr($self));
          $self->{ large_key } = $large_key;
          $larges{ $large_key } = $large;
          }

          return $self;
          }

          sub DESTROY {
          my ($self) = @_;
          if (defined( my $large_key = $self->{ large_key } )) {
          delete( $larges{ $large_key } );
          }
          }

          sub STORABLE_freeze {
          my ( $self, $cloning ) = @_;
          if ($cloning) {
          return ( $self->{large_key}, $self->{tiny} );
          } else {
          return ( "", $self->{tiny}, $larges{ $self->{large_key} } );
          }
          }

          sub STORABLE_thaw {
          my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
          if ($cloning) {
          my ($tiny) = @_;
          my $large_key = $serialized;
          $self->_init($tiny, $larges{ $large_key });
          } else {
          $self->_init(@_);
          }
          }


          Untested.



          No memory leaks if the cloning process dies.







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Nov 13 at 15:24

























          answered Nov 12 at 16:24









          ikegami

          261k11176396




          261k11176396












          • Thanks, I added reference counts. Great idea!
            – Peter V. Mørch
            Nov 13 at 15:22










          • Fixed a bug in that code
            – ikegami
            Nov 13 at 15:25


















          • Thanks, I added reference counts. Great idea!
            – Peter V. Mørch
            Nov 13 at 15:22










          • Fixed a bug in that code
            – ikegami
            Nov 13 at 15:25
















          Thanks, I added reference counts. Great idea!
          – Peter V. Mørch
          Nov 13 at 15:22




          Thanks, I added reference counts. Great idea!
          – Peter V. Mørch
          Nov 13 at 15:22












          Fixed a bug in that code
          – ikegami
          Nov 13 at 15:25




          Fixed a bug in that code
          – ikegami
          Nov 13 at 15:25


















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53265483%2fcustom-storable-hooks-for-dclone-ing-a-light-weight-object-referencing-a-heavy-w%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Bressuire

          Vorschmack

          Quarantine