Cryptic Moo (Perl) Ошибка "Попытка благословить ссылку на..."

Вероятно, это длинный путь, но мне интересно, видел ли кто-нибудь подобную ошибку раньше, поскольку я не могу воспроизвести ее вне рабочей среды. По сути, ситуация такова:

  1. У меня есть модуль под названием My::Budget::Module (переименован для простоты), который отвечает за обновление "бюджета" для данного объекта в приложении
  2. My::Budget::Module использует Moo объект, который я построил, называется My::Bulk::Update::Module который делает следующее:
    • создать массив строк базы данных, которые необходимо обновить
    • создайте строку / оператор запроса на обновление MySQL, который обновит все строки одновременно
    • на самом деле обновить все строки сразу
  3. My::Bulk::Update::Module затем выполнит обновление и пометит строки, которые были обновлены, как "устаревшие", чтобы они не были кэшированы

Кажется, ошибка всегда возникает где-то после добавления строки, подлежащей обновлению, но до возврата кода, который фактически применяет обновление.

Если вы посмотрите на трассировку стека, которую я включил ниже, вы увидите, что ошибка принимает вид

Attempt to bless into a reference at...

и точка, в которой это происходит, находится в конструкторе Moo/Object.pm который Version 2.003002 из Moo от cpan(см. здесь).

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::Collection а также Moo::Object создаются с первым аргументом My::Bulk::Update::Module=HASH(0xf784b50), Учитывая логику приложения я не верю MongoDB::Collection должны быть созданы здесь и не должны My::Bulk::Update::Module быть переданным MongoDB::Collection совсем.

Кроме того факта, что это Moo объект, My::Bulk::Update::Module не расширяет какой-либо другой модуль и предназначен для использования в качестве отдельного "служебного" модуля. Он используется только в одном месте во всем приложении.

Кто-нибудь видел что-то подобное раньше?

РЕДАКТИРОВАТЬ: Добавление еще кода - apply_bulk_update мало что делает вообще. Там нет вызова MongoDB::Collection здесь и MongoDB::Collection в данном конкретном примере просто "случается" быть модулем, включенным в трассировку стека. Это не всегда MongoDB::Collection - Я также видел MongoDB::Timestamp, MongoDB::Cursor, Search::Elasticsearch::Serializer::JSON, Search::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 а иногда после выполнения запроса на последней строке...

1 ответ

Решение

На всякий случай кому-то было интересно...

После части дальнейшей отладки ошибка была прослежена до точной точки, где My::Bulk::Update::Module::apply_bulk_update или же My::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

Если вы прочитаете сводку коммита, то увидите, что было сделано изменение:

Запретите defer_info и undefer_sub работать с просроченными подпрограммами. Проверьте, что аргументы defer_info и undefer_sub ссылаются на фактические живые подпрограммы. Используйте слабые ссылки, которые мы храним для отложенных и отложенных подпрограмм, чтобы убедиться, что исходные подпрограммы все еще живы, и мы не возвращаем данные, связанные с повторно используемым адресом памяти. Также убедитесь, что мы не истекаем данные, связанные с неназванными подпрограммами. Так как пользователь может захватить подчиненную подпрограмму через undefer_sub, мы не можем отследить истечение срока действия без использования хеша поля. А пока избегайте этой сложности, поскольку количество, которое мы пропускаем, не должно быть таким большим.

Обновление версии Sub::Defer кажется, решил проблему.

Другие вопросы по тегам