mongodbperlcpanmoobless

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


Probably a long shot but I'm wondering if anyone has seen an error like this before, as I can not reproduce it outside of a production environment. Essentially the situation is as follows:

  1. I have a module called My::Budget::Module (renamed for simplicity) which is responsible for updating the "budget" for a given object in the application
  2. The My::Budget::Module uses a Moo object that I built called My::Bulk::Update::Module which does the following:
    • build up an array of database rows that need to be updated
    • build a MySQL update query string / statement which will update all rows at once
    • actually update all rows at once
  3. The My::Bulk::Update::Module will then perform the update and mark the rows that have been updated as "stale" so that they will not be cached

The error always seems to occur somewhere after adding a row to be updated but before the code which actually applies the update returns.

If you look at the stack trace that I have included below you can see that the error takes the form

Attempt to bless into a reference at...

and the point at which this occurs is in the constructor of Moo/Object.pm which is Version 2.003002 of Moo from cpan(see 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

Moving backwards through the stack trace leads to MongoDB::Collection & this is where things start to get very weird.

MongoDB::Collection is also a cpan module but the module which appears at this point varies and I can't see a pattern here except that it is always a Moo object. Moreover, I'm unsure why this module is being instantiated as there is no call to MongoDB::Collection::new at the line mentioned.

In addition, from the stack trace it looks like MongoDB::Collection and Moo::Object are instantiated with the first argument being My::Bulk::Update::Module=HASH(0xf784b50). Given the application logic I do not believe MongoDB::Collection should be instantiated here nor should My::Bulk::Update::Module be passed to MongoDB::Collection at all.

Other than the fact that it is a Moo object, My::Bulk::Update::Module does not extend any other module and is designed to be a stand alone "utility" module. It is only used at one place in the entire application.

Has anyone seen something similar before?

EDIT: Adding some more code - apply_bulk_update doesn't do much at all. There is no call to MongoDB::Collection here and MongoDB::Collection just "happens" to be the moudule included in the stack trace in this particular example. This is not always MongoDB::Collection - I've also seen MongoDB::Timestamp, MongoDB::Cursor, Search::Elasticsearch::Serializer::JSON, Search::Elasticsearch::Logger::LogAny etc etc

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

The code sometimes dies as soon as apply_bulk_update is called, sometimes on the call to _generate_query and sometimes after the query executes on the last line...


Solution

  • Just in case anyone was interested...

    After a chunk of further debugging the error was traced to the exact point where My::Bulk::Update::Module::apply_bulk_update or My::Bulk::Update::Module::_generate_query was called but logging code inside these subroutines determined that they were not being executed as expected.

    To determine what was going on B::Deparse was used to rebuild the source code for the body of these subroutines (or at least the source code located at the memory address to which these subs were pointing)

    After using this library e.g.

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

    it became obvious that the error occurred when My::Bulk::Update::_generate_query was pointing at a memory location which contained something entirely different (i.e. MongoDB::Collection::new etc).

    This issue appears to have been solved upstream by the following commit in the Sub::Defer module (which is a dependency for Moo).

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

    If you read the summary of the commit you can see the change that was made:

    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.

    Upgrading the version of Sub::Defer appears to have solved the issue.