【问题标题】:Safely freeing resources in XS code (running destructors on scope exit)在 XS 代码中安全地释放资源(在范围退出时运行析构函数)
【发布时间】:2018-01-28 19:29:25
【问题描述】:

我正在编写一个 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 的空指针。 _X 版本适用于使用pTHX_ 宏参数声明的函数。

我在正确的轨道上吗?我应该酌情使用这些宏吗?它们是在哪个 Perl 版本中引入的?关于它们的使用是否有任何进一步的指导?究竟什么时候触发析构函数?大概与FREETMPSLEAVE 宏有关?

【问题讨论】:

  • 顺便问下好问题...
  • Perl API 通常只会在无效输入时发出嘶嘶声。如果您调用 Perl API 的方式可能会发牢骚,您只需自己检查参数的有效性即可。唯一的例外是调用 Perl 代码,您可以在其中使用 G_EVAL flag
  • @nwellnhof,您忘记了致命警告(我在上面提到过)。检查你是否真的得到了一个数字而不是使用SvIV 是不切实际的。但这可能会警告(未初始化或非数字),这可能会死。
  • @ikegami 我正在分配执行 XS 函数期间所需的临时数据结构(缓冲区、队列、图表)。它们不会作为 SV 返回。它们不受某些常量的限制,因此不能进行堆栈分配。我添加了一段伪代码来说明我的问题的结构。
  • 我不知道SAVEDESTRUCTOR,这似乎是一种运行清理代码的防弹方法。不过,我不会使用 Perl 异常来处理我自己的内部 C 函数中的错误。相反,我更愿意返回错误代码并在释放所有资源后抛出 XSUB。这对于许多典型情况应该已经足够了,比如从AVHV 中提取数据。即使你的库的用户可以偷偷输入一些奇怪的数据,使 Perl API 函数抛出异常并导致内存泄漏,我也不会太担心,除非它绝对是关键任务代码。

标签: c perl destructor xs perl-xs


【解决方案1】:

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

因此,我假设SAVEDESTRUCTOR 应该用作清理的“finally”块,并且足够安全和​​稳定。

摘自Localizing changes in perlguts,其中讨论了与{ local $foo; ... } 块等效的内容:

有一种方法可以通过 Perl API 从 C 中实现类似的任务:创建一个 伪块,并安排在其末尾自动撤消一些更改,无论是显式的,还是通过非本地出口(通过 die())。 block 类结构由一对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)

    pseudo-block 的末尾,函数 f 使用隐式上下文参数(如果有)和 p 调用。

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

这些宏基本上一直可用,至少 Perl 5.6 或更早版本。

【讨论】:

    猜你喜欢
    • 2018-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-09
    • 1970-01-01
    • 2013-03-14
    • 2011-10-31
    • 1970-01-01
    相关资源
    最近更新 更多