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:
My::Budget::Module
(renamed for simplicity) which is responsible for updating the "budget" for a given object in the applicationMy::Budget::Module
uses a Moo
object that I built called My::Bulk::Update::Module
which does the following:
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 cachedThe 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...
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.