安全地释放XS代码中的资源(在范围退出时运行析构函数)

时间:2017-08-20 17:31:03

标签: c perl destructor xs perl-xs

我正在写一个XS模块。我分配了一些资源(例如malloc()SvREFCNT_inc())然后执行一些涉及Perl API的操作,然后释放资源。这在普通C中很好,因为C没有例外,但使用Perl API的代码可能croak(),因此阻止了正常清理和泄漏资源。因此,除了相当简单的情况外,似乎不可能编写正确的XS代码。

当我自己croak()时,我可以清理到目前为止分配的任何资源,但我可能会直接调用croak()的函数来回避我写的任何清理代码。

伪代码来说明我的担忧:

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);
  }

那么如何安全地清理XS代码中的资源呢?如何注册在抛出异常时运行的析构函数,或者从XS代码返回到Perl代码时运行的析构函数?

到目前为止我的想法和发现:

  • 我可以创建一个在析构函数中运行必要清理的类,然后创建一个包含此类实例的凡人SV。在未来的某个时刻,Perl将释放该SV并运行我的析构函数。然而,这似乎相当倒退,必须有更好的方法。

  • XSAWYERX的XS Fun小册子似乎详细讨论了DESTROY方法,但没有处理在 XS代码中产生的异常。

  • LEONT的Scope::OnExit模块功能XS code使用SAVEDESTRUCTOR()SAVEDESTRUCTOR_X()宏。这些似乎没有记录。

  • Perl API列出save_destructor()save_destructor_x()列为公开但未记录的内容。

  • Perl的scope.h标头(由perl.h包含)声明了SAVEDESTRUCTOR(f,p)SAVEDESTRUCTOR_X(f,p)个宏,没有任何进一步的解释。从上下文和Scope::OnExit代码判断,f是一个函数指针,p是一个将传递给f的void指针。 _X版本用于使用pTHX_宏参数声明的函数。

我是否在正确的轨道上?我应该酌情使用这些宏吗?他们介绍了Perl版本?是否有关于其使用的进一步指导?什么时候触发了析构函数?大概是在与FREETMPSLEAVE宏相关的点上?

1 个答案:

答案 0 :(得分:5)

经过进一步的研究,事实证明SAVEDESTRUCTOR实际上已记录在perlguts而不是perlapi。那里记录了确切的语义。

因此,我认为SAVEDESTRUCTOR应该被用作"最后"阻止清理,并且足够安全和​​稳定。

摘自Localizing changes in perlguts,其中讨论了{ local $foo; ... }块的等价物:

  

有一种方法可以通过Perl API从C实现类似的任务:创建一个伪块,并安排一些更改在结束时自动撤消,无论是显式的,还是通过非本地出口(通过die())。 block -like构造由一对ENTER / LEAVE宏创建(请参阅Returning a Scalar in perlcall)。可以专门为某些重要的本地化任务创建这样的构造,或者可以使用现有的构造(如封闭Perl子例程/块的边界,或者用于释放TMP的现有对)。 (在第二种情况下,额外本地化的开销几乎可以忽略不计。)请注意,任何XSUB都自动包含在ENTER / LEAVE对中。

     

在这样的伪块中,可以使用以下服务:

     
      
  • [...]

  •   
  • <强> SAVEDESTRUCTOR(DESTRUCTORFUNC_NOCONTEXT_t f, void *p)

         

    伪块的末尾,使用唯一参数f调用函数p

  •   
  • <强> SAVEDESTRUCTOR_X(DESTRUCTORFUNC_t f, void *p)

         

    伪块的末尾,使用隐式上下文参数(如果有)和f调用函数p

  •   

该部分还列出了一些专门的析构函数,例如SAVEFREESV(SV *sv)SAVEMORTALIZESV(SV *sv),在某些情况下可能比早期sv_2mortal()更正确。

这些宏基本上可以永久使用,至少可以使用Perl 5.6或更早版本。