Skip Menu |

This queue is for tickets about the Object-Pad CPAN distribution.

Report information
The Basics
Id: 132228
Status: resolved
Priority: 0/
Queue: Object-Pad

People
Owner: Nobody in particular
Requestors: leonerd-cpan [...] leonerd.org.uk
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in: 0.15
Fixed in: 0.16



Subject: Causes memory reference increases
When converting Tickit-Widgets/t/01widget.t to use Object::Pad for the internal TestWidget class: # Failed test '$widget has refcount 1 initially' # at t/01widget.t line 15. # expected 1 references, found 2 # SV address is 0x5573ba95f580 # Writing heap dump to t/01widget-2.pmat -- Paul Evans
Initial investigation: Show quoted text
pmat> show 0x5573ba95f580
HASH(5)=TestWidget at 0x5573ba95f580 with refcount 2 size 240 bytes blessed as TestWidget 5 values (use 'values' command to show) Show quoted text
pmat> values 0x5573ba95f580
{"Object::Pad/slots"} REF() at 0x5573bb427720 => ARRAY(0) at 0x5573bb194920 {classes} REF() at 0x5573ba95f640 => ARRAY(1) at 0x5573ba95f478 {pen} REF() at 0x5573bb0f0a00 => SCALAR(UV)=Tickit::Pen::Immutable at 0x5573bb427858 = 93955545493040 {style_cache} REF() at 0x5573bb427678 => HASH(1) at 0x5573bb1948f0 {style_pen_cache} REF() at 0x5573ba95f598 => HASH(1) at 0x5573ba95f658 Show quoted text
pmat> identify 0x5573ba95f580
HASH(5)=TestWidget at 0x5573ba95f580 is: ├─(via RV) the lexical $self at depth 1 of CODE(PP) at 0x5573bb1948a8, which is: │ └─the symbol '&TestWidget::INITSLOTS' └─(via RV) the lexical $widget at depth 1 of CODE() at 0x5573ba93c6c8=main_cv, which is: └─the main code suggests it's still live in the pad of INITSLOTS, which means the pad didn't get cleared at leave time. Probably something I haven't done with initial/final sequence numbers on those slots. -- Paul Evans
Fixed by attached patch. -- Paul Evans
Subject: rt132228.patch
=== modified file 'Build.PL' --- old/Build.PL 2020-03-18 17:51:49 +0000 +++ new/Build.PL 2020-03-25 19:12:24 +0000 @@ -14,6 +14,7 @@ test_requires => { 'Data::Dump' => 0, 'Test::More' => '0.88', # done_testing + 'Test::Refcount' => 0, }, configure_requires => { 'Module::Build' => '0.4004', # test_requires === modified file 'lib/Object/Pad.xs' --- old/lib/Object/Pad.xs 2020-03-20 22:21:32 +0000 +++ new/lib/Object/Pad.xs 2020-03-25 19:12:24 +0000 @@ -172,10 +172,11 @@ PADIX_SLOTS = 2, }; -static OP *newPADSVOP(PADOFFSET padix, I32 flags) +static OP *newPADSVOP(PADOFFSET padix, I32 flags, U32 private) { OP *op = newOP(OP_PADSV, flags); op->op_targ = padix; + op->op_private = private; return op; } @@ -192,6 +193,7 @@ if(!sv_derived_from(self, HvNAME(classstash))) croak("Cannot invoke foreign method on non-derived instance"); + save_clearsv(&PAD_SVl(PADIX_SELF)); sv_setsv(PAD_SVl(PADIX_SELF), self); SV *slotsav; @@ -216,7 +218,8 @@ } } - PAD_SVl(PADIX_SLOTS) = slotsav; + save_clearsv(&PAD_SVl(PADIX_SLOTS)); + PAD_SVl(PADIX_SLOTS) = SvREFCNT_inc(slotsav); return PL_op->op_next; } @@ -390,7 +393,8 @@ ops = op_append_list(OP_LINESEQ, ops, /* $self = shift */ - newBINOP(OP_SASSIGN, 0, newOP(OP_SHIFT, 0), newPADSVOP(PADIX_SELF, OPf_MOD))); + newBINOP(OP_SASSIGN, 0, newOP(OP_SHIFT, 0), + newPADSVOP(PADIX_SELF, OPf_MOD, OPpLVAL_INTRO))); intro_my(); @@ -404,7 +408,7 @@ /* Build an OP_ENTERSUB for $self->SUPER::INITSLOTS() */ OP *op = NULL; op = op_append_list(OP_LIST, op, - newPADSVOP(PADIX_SELF, 0)); + newPADSVOP(PADIX_SELF, 0, 0)); op = op_append_list(OP_LIST, op, newMETHOD_REDIR_OP(superclass, newSVpvn_share("INITSLOTS", 9, 0), 0)); @@ -424,13 +428,13 @@ switch(meta->repr) { case REPR_NATIVE: /* $self->@* */ - slotsavop = newUNOP(OP_RV2AV, OPf_MOD|OPf_REF, newPADSVOP(PADIX_SELF, 0)); + slotsavop = newUNOP(OP_RV2AV, OPf_MOD|OPf_REF, newPADSVOP(PADIX_SELF, 0, 0)); break; case REPR_FOREIGN_HASH: /* $self->{"Object::Pad/slots"}->@* */ slotsavop = newUNOP(OP_RV2AV, OPf_MOD|OPf_REF, newBINOP(OP_HELEM, OPf_MOD, - newUNOP(OP_RV2HV, OPf_MOD|OPf_REF, newPADSVOP(PADIX_SELF, 0)), + newUNOP(OP_RV2HV, OPf_MOD|OPf_REF, newPADSVOP(PADIX_SELF, 0, 0)), newSVOP(OP_CONST, 0, newSVpvs("Object::Pad/slots")))); break; } @@ -514,7 +518,7 @@ switch(meta->repr) { case REPR_NATIVE: CopLINE_set(PL_curcop, __LINE__); - self = newRV_noinc((SV *)newAV()); + self = sv_2mortal(newRV_noinc((SV *)newAV())); sv_bless(self, meta->stash); break; === modified file 't/03create.t' --- old/t/03create.t 2019-11-19 12:03:22 +0000 +++ new/t/03create.t 2020-03-25 19:12:24 +0000 @@ -4,6 +4,7 @@ use warnings; use Test::More; +use Test::Refcount; use Object::Pad; @@ -20,7 +21,10 @@ { my $p = Point->new( 10, 20 ); + is_oneref( $p, '$p has refcount 1 initially' ); + is( $p->where, "(10,20)", '$p->where' ); + is_oneref( $p, '$p has refcount 1 after method' ); } done_testing; === modified file 't/80async-method.t' --- old/t/80async-method.t 2020-03-24 16:10:03 +0000 +++ new/t/80async-method.t 2020-03-25 19:12:24 +0000 @@ -4,6 +4,7 @@ use warnings; use Test::More; +use Test::Refcount; BEGIN { plan skip_all => "Future is not available" @@ -38,14 +39,19 @@ } my $thunker = Thunker->new; + is_oneref( $thunker, 'after ->new' ); my $f1 = Future->new; my $fret = $thunker->thunk( $f1 ); + is_refcount( $thunker, 3, 'during async sub' ); + # +1 because $self, +1 because of @(Object::Pad/slots) pseudolexical is( $thunker->count, 0, 'count is 0 before $f1->done' ); $f1->done; + is_oneref( $thunker, 'after ->done' ); + is( $thunker->count, 1, 'count is 1 after $f1->done' ); is( $fret->get, "result", '$fret for await in async method' ); }
Fixed in 0.16 -- Paul Evans