Cryptic Moo (Perl) 错误 "Attempt to bless into a reference at..."

Cryptic Moo (Perl) Error "Attempt to bless into a reference at..."

可能不太可能,但我想知道以前是否有人见过这样的错误,因为我无法在生产环境之外重现它。基本上情况如下:

  1. 我有一个名为 My::Budget::Module 的模块(为简单起见重命名),它负责更新应用程序中给定对象的 "budget"
  2. My::Budget::Module 使用我构建的名为 My::Bulk::Update::ModuleMoo 对象,它执行以下操作:
    • 构建需要更新的数据库行数组
    • 构建一个 MySQL 更新查询字符串/语句,它将一次更新所有行
    • 实际上一次更新所有行
  3. 然后My::Bulk::Update::Module会执行更新,将已经更新的行标记为"stale",这样就不会被缓存

错误似乎总是发生在添加要更新的行之后但实际应用更新的代码之前的某处returns。

如果您查看我在下面包含的堆栈跟踪,您会发现错误采用

的形式

Attempt to bless into a reference at...

并且发生这种情况的点是在 Moo/Object.pm 的构造函数中,它是 cpanMooVersion 2.003002(请参阅 here)。

Attempt to bless into a reference at /path/to/module/from/cpan/Moo/Object.pm line 25 at /path/to/module/from/cpan/Moo/Object.pm line 25.
Moo::Object::new(My::Bulk::Update::Module=HASH(0xf784b50)) called at (eval 1808) line 28
MongoDB::Collection::new(My::Bulk::Update::Module=HASH(0xf784b50)) called at /path/to/my/bulk/update/module line XXXX
My::Bulk::Update::Module::apply_bulk_update(My::Bulk::Update::Module=HASH(0xf784b50)) called at /path/to/my/budget/module line XXXX
My::Budget::Module::update_budget(My::Budget::Module=HASH(0xf699a38)) called at /path/to/my/budget/module line XXXX

通过堆栈跟踪向后移动导致 MongoDB::Collection & 这是事情开始变得非常奇怪的地方。

MongoDB::Collection 也是一个 cpan 模块,但此时出现的模块各不相同,除了它始终是一个 Moo 对象外,我在这里看不到任何模式。此外,我不确定为什么要实例化此模块,因为在提到的行中没有调用 MongoDB::Collection::new

此外,从堆栈跟踪来看,MongoDB::CollectionMoo::Object 是用第一个参数 My::Bulk::Update::Module=HASH(0xf784b50) 实例化的。考虑到应用程序逻辑,我认为 MongoDB::Collection 不应在此处实例化,也不应将 My::Bulk::Update::Module 传递给 MongoDB::Collection

除了它是一个 Moo 对象之外,My::Bulk::Update::Module 不扩展任何其他模块并且被设计为一个独立的 "utility" 模块。它在整个应用程序中仅在一个地方使用。

有没有人以前见过类似的东西?

编辑: 添加更多代码 - apply_bulk_update 一点作用都没有。此处没有调用 MongoDB::CollectionMongoDB::Collection 只是 "happens" 成为此特定示例中堆栈跟踪中包含的模块。这并不总是 MongoDB::Collection - 我也见过 MongoDB::TimestampMongoDB::CursorSearch::Elasticsearch::Serializer::JSONSearch::Elasticsearch::Logger::LogAny 等等

sub apply_bulk_update
{
    my $self = shift;
    my ($db) = @_; # wrapper around DBI module

    my $query  = $self->_generate_query(); # string UPDATE table SET...
    my $params = $self->_params; # arrayref

    return undef unless $params && scalar @$params;

    $db->do($query, undef, @$params);        
}

代码有时会在调用 apply_bulk_update 时立即终止,有时会在调用 _generate_query 时终止,有时会在最后一行执行查询后...

万一有人感兴趣...

经过进一步调试,错误被追踪到 My::Bulk::Update::Module::apply_bulk_updateMy::Bulk::Update::Module::_generate_query 被调用的确切点,但这些子例程中的日志记录代码确定它们没有按预期执行。

确定发生了什么 B::Deparse 用于重建这些子例程主体的源代码(或者至少是位于这些子程序指向的内存地址的源代码)

使用这个库后,例如

B::Deparse->new->coderef2text(\&My::Bulk::Update::_generate_query)

My::Bulk::Update::_generate_query 指向包含完全不同的内容(即 MongoDB::Collection::new 等)的内存位置时,很明显会发生错误。

此问题似乎已通过 Sub::Defer 模块(这是 Moo 的依赖项)中的以下提交在上游解决。

https://github.com/moose/Sub-Quote/commit/4a38f034366e79b76d29fec903d8e8d02ee01896

如果您阅读提交摘要,您可以看到所做的更改:

Prevent defer_info and undefer_sub from operating on expired subs. Validate that the arguments to defer_info and undefer_sub refer to actual live subs. Use the weak refs we are storing to the deferred and undeferred subs to make sure the original subs are still alive, and we aren't returning data related to a reused memory address. Also make sure we don't expire data related to unnamed subs. Since the user can capture the undeferred sub via undefer_sub, we can't track the expiry without using a fieldhash. For now, avoid introducing that complexity, since the amount we leak should not be that great.

升级 Sub::Defer 的版本似乎已经解决了这个问题。