I am writing an XS module. I allocate some resource (e.g. malloc()
or SvREFCNT_inc()
) then do some operations involving the Perl API, then free the resource. This is fine in normal C because C has no exceptions, but code using the Perl API may croak()
, thus preventing normal cleanup and leaking the resources. It therefore seems impossible to write correct XS code except for fairly simple cases.
When I croak()
myself I can clean up any resources allocated so far, but I may be calling functions that croak()
directly which would sidestep any cleanup code I write.
Pseudo-code to illustrate my concern:
static void some_other_function(pTHX_ Data* d) {
...
if (perhaps) croak("Could not frobnicate the data");
}
MODULE = Example PACKAGE = Example
void
xs(UV n)
CODE:
{
/* Allocate resources needed for this function */
Data* object_graph;
Newx(object_graph, 1, Data);
Data_init(object_graph, n);
/* Call functions which use the Perl API */
some_other_function(aTHX_ object_graph);
/* Clean up before returning.
* Not run if above code croak()s!
* Can this be put into the XS equivalent of a "try...finally" block?
*/
Data_destroy(object_graph);
Safefree(object_graph);
}
So how do I safely clean up resources in XS code? How can I register some destructor that is run when exceptions are thrown, or when I return from XS code back to Perl code?
My ideas and findings so far:
I can create a class that runs necessary cleanup in the destructor, then create a mortal SV containing an instance of this class. At some point in the future Perl will free that SV and run my destructor. However, this seems rather backwards, and there has to be a better way.
XSAWYERX's XS Fun booklet seems to discuss DESTROY methods at great length, but not the handling of exceptions that originate within XS code.
LEONT's
Scope::OnExit
module features XS code usingSAVEDESTRUCTOR()
andSAVEDESTRUCTOR_X()
macros. These do not seem to be documented.The Perl API lists
save_destructor()
andsave_destructor_x()
functions as public but undocumented.Perl's
scope.h
header (included byperl.h
) declaresSAVEDESTRUCTOR(f,p)
andSAVEDESTRUCTOR_X(f,p)
macros, without any further explanation. Judging from context and theScope::OnExit
code,f
is a function pointer andp
a void pointer that will be passed tof
. The _X version is for functions that are declared with thepTHX_
macro parameter.
Am I on the right track with this? Should I use these macros as appropriate? In which Perl version were they introduced? Is there any further guidance available on their use? When precisely are the destructors triggered? Presumably at a point related to the FREETMPS
or LEAVE
macros?
croak()
, won't everything befree()
d as the program process(es) finish anyways? – MochunSV* sv = sv_2mortal(newSV()); ... return sv;
instead ofSV* sv = newSV(); ... return sv_2mortal(sv);
– Otoplastymalloc()
ing orNewx()
ing complex C data structures that need a cleanup function to run. One of my ideas is to store an object with suitable cleanup code in a mortal SV which does get the job done. It's just very involved and inconvenient and unlikely that there isn't a better way – which led me to theSAVEDESTRUCTOR
macros. – VerifiedG_EVAL flag
. – ShivaSvIV
. But that can warn (uninitialized, or not numeric), which can die. – OtoplastyPOSIX::sigaction
registers a destructor to reset a sigmask.threads::shared
registers a destructor to release a lock under all circumstances before the xsub returns. The Perl MongoDB driver registers a destructor to free a C-level parser object, which is necessary as the parsing functions may croak. That's very similar to my use case. – VerifiedSAVEDESTRUCTOR
and it seems like a bulletproof way to run cleanup code. I wouldn't use Perl exceptions to handle errors in my own internal C functions, though. Instead, I'd prefer to return error codes and make the XSUB throw after releasing all resources. This should be good enough for many typical cases, like extracting data from anAV
orHV
. Even if there's a way that users of your library can sneak in some weird data that makes a Perl API function throw an exception and cause a memory leak, I wouldn't be too worried unless it's absolutely mission critical code. – Shiva