Why is this XS code returning PerlIO * leaky?

I am trying to write some XS code that provides a part of the library for Perl code as a stream interface that can be written to. get_stream function below should be a constructor that prepares and returns a PerlIO object. I decided that I only needed Write and Close , so I left all the other function slots empty.

 typedef struct { struct _PerlIO base; mylib_context* ctx; } PerlIOmylib; /* [...] */ PERLIO_FUNCS_DECL(PerlIO_mylib_funcs) = { .fsize = sizeof(PerlIO_funcs), .name = "mylib", .size = sizeof(PerlIOmylib, .Write = mylib_write, .Close = mylib_close, }; /* XS below */ PerlIO* get_stream (SV* context_obj) CODE: mylib_context* ctx = (mylib_context*) SvIV (SvRV (context_obj)); PerlIO* f = PerlIO_allocate (aTHX); f = PerlIO_push (aTHX, f, PERLIO_FUNCS_CAST(&PerlIO_mylib_funcs), "a", NULL); PerlIOSelf(f, PerlIOmylib)->ctx = ctx; PerlIOBase(f)->flags |= PERLIO_F_OPEN; RETVAL = f; OUTPUT: RETVAL 

When I use the provided interface like this ...

 { my $fh = MyLib::get_stream($lib_ctx); print $fh "x" x 300; } 

... calls the mylib_write function, so I haven't completely so far. (I checked this by inserting debug printf). However, I would like the PerlIO object to be closed when $fh goes beyond just how everything works with regular files created using open . But at the moment, the mylib_close function is called only when the interpreter is turned off.

Directly calling Close works fine, setting $fh to undef does not.

UPDATE: Following ikegami's advice, I used Devel::Peek::Dump and sv_dump and found out that the get_stream function returned by the descriptor is "RV", which points to a SV = PVGV(...) . Glob ( PVGV ) has its link count set to 3, which seems to be wrong.

I added

 CLEANUP: SvREFCNT_dec (SvRV (ST(0))); SvREFCNT_dec (SvRV (ST(0))); 

which cures the symptom: The Close function is called when $fh goes out of scope at the end of the block. But I still do not quite understand the underlying problem.

This is the C code generated for the OUTPUT section:

 ST(0) = sv_newmortal(); { GV *gv = newGVgen("MyLib"); if (do_open(gv, "+<&", 3, FALSE, 0, 0, RETVAL) ) sv_setsv(ST(0), sv_bless(newRV((SV*)gv), gv_stashpv("MyLib",1))); else ST(0) = &PL_sv_undef; } XSRETURN(1); 

How does GV link counting end at 3?

+7
source share
2 answers

If close is called during global destruction, it means that your handle still exists during global destruction. You are seeping!

In C / XS code, you can use sv_dump(sv) to reset the scalar to stderr. In Perl code, you can use Devel :: Peek Dump to get the same functionality. This will show you the number of links.


In response to your new question,

You have three distributions, but only one release (deferred from sv_2mortal).

  • gv : The pointer is always discarded. Memory leak!

    You can either reduce the value of ref gv on error, or unconditionally refuse refcnt after using newRV_inc to transfer ownership of the RV on opening.

  • SV from newRV : the pointer is always discarded. Memory leak!

    Why not just return it and not copy it? Just mark it as mortal to make Perl reduce its refcnt after the caller receives it.

Fixed

 { GV *gv = newGVgen("MyLib"); if (!do_open(gv, "+<&", 3, FALSE, 0, 0, RETVAL) ) { SvREFCNT_dec(gv); XSRETURN_UNDEF; } ST(0) = sv_2mortal(sv_bless(newRV_noinc((SV*)gv), gv_stashpv("MyLib",1)))); XSRETURN(1); } 
+7
source

I just reproduced the problem with a trivial example:

 $ h2xs -n foo Defaulting to backwards compatibility with perl 5.14.2 If you intend this module to be compatible with earlier perl versions, please specify a minimum perl version with the -b option. Writing foo/ppport.h Writing foo/lib/foo.pm Writing foo/foo.xs Writing foo/fallback/const-c.inc Writing foo/fallback/const-xs.inc Writing foo/Makefile.PL Writing foo/README Writing foo/t/foo.t Writing foo/Changes Writing foo/MANIFEST 

In foo/foo.xs I added:

 PerlIO* get_stream(char* name); CODE: RETVAL = PerlIO_open (name, "w"); OUTPUT: RETVAL 

and the following trivial test program:

 #!/usr/bin/perl use foo; use Devel::Peek; { my $fh = foo::get_stream ("testfile"); Devel::Peek::Dump $fh; print $fh "hello\n"; } print "bye\n"; 

Of course, the link count of the generated ball is set to 3, and strace shows that closing the file descriptor is the last thing that the Perl interpreter does.

Thus, the default PerlIO* processing PerlIO* to be PerlIO* : - (

The following typemap fragment seems to fix this (thanks, ikegami!):

 TYPEMAP PerlIO * T_PIO OUTPUT T_PIO { GV *gv = newGVgen("$Package"); if (do_open(gv, "+<&", 3, FALSE, 0, 0, $var) ) { $arg = sv_2mortal(sv_bless(newRV_noinc((SV*)gv), gv_stashpv("$Package",1))); } else { SvREFCNT_dec(gv); $arg = &PL_sv_undef; } } 
0
source

All Articles