Контроль порядка уничтожения объекта - PullRequest
2 голосов
/ 03 октября 2019

Мое веб-приложение использует модуль Log для записи различных событий. Объект журнала инициализируется передачей объекта CGI :: Session, содержащего различную информацию о пользователе. Эта информация копируется в поля данных объекта журнала. В связи с объемом активности на сайте и тем фактом, что одно посещение сайта может привести к нескольким регистрируемым событиям, модуль журнала в настоящее время кэширует все события в памяти, а затем фактически записывает их в файл журнала в функции DESTROY. Однако это приводит к зависанию параметров сеанса во время инициализации объекта журнала, что происходит в начале запроса.

В последнее время необходимо было зарегистрировать некоторые новые параметры, которые а) будут сохраняться в объекте сеанса, и б) должны регистрироваться как их конечное значение, а не как исходное значение (и потенциально могут измениться во время выполнения),Моя первоначальная идея заключалась в том, чтобы вместо этого сохранить ссылку на объект сеанса в объекте журнала, но так как функция DESTROY обычно вызывается при глобальном уничтожении, я не могу гарантировать, что объект сеанса все еще будет определен при уничтожении журнала. Есть ли способ гарантировать, что объект CGI :: Session не будет уничтожен перед моим журналом, надеюсь, без необходимости явного уничтожения каждой страницы приложения?

#old
package Log;

sub new
{
  my $class = shift;
  my $session = shift; #CGI::Session

  my $self = {session => {customer_id => $session->param('customer_id')}, events => []};
  return bless $self, $class;
}

sub log_event
{
  my $self = shift;
  my $event = shift;

  push @{$self->{'events'}}, {event_type => $event->{'type'}, timestamp => $event->{'timestamp'}};
}

sub DESTROY
{
  my $self = shift;
  if (scalar @{$self->{'events'}})
  {
    open LOG, "/tmp/log";
    print LOG, Dumper({session => $self->{'session'}, events => $self->{'events'}});
    close LOG;
  }
}

#new 
package Log;

sub new
{
  my $class = shift;
  my $session = shift;#CGI::Session

  my $self = {session => $session, events => []};
  return bless $self, $class;
}

sub log_event
{
  my $self = shift;
  my $event = shift;

  push @{$self->{'events'}}, {event_type => $event->{'type'}, timestamp => $event->{'timestamp'}};
}

sub DESTROY
{
  my $self = shift;
  if (scalar @{$self->{'events'}})
  {
    open LOG, "/tmp/log";
    print LOG, Dumper({session => {customer_id => $self->{'session'}->param('customer_id')}}, events => $self->{'events'}});
    close LOG;
  }
}

1 Ответ

3 голосов
/ 03 октября 2019

Perl использует подсчет ссылок для управления уничтожением объекта [1] . Это означает, что при нормальных обстоятельствах, если объект A ссылается на объект B, объект A будет уничтожен до объекта B.

Это не удастся, если объект выживет до глобального уничтожения. Это происходит при двух обстоятельствах:

  • Контрольный цикл. Если два объекта прямо или косвенно ссылаются друг на друга, порядок уничтожения объектов, участвующих в циклах, непредсказуем.

  • Глобальная переменная. Порядок, в котором объекты, на которые ссылаются переменные пакета (и, следовательно, объекты, на которые они прямо или косвенно ссылаются), непредсказуем (хотя Perl пытается сделать все правильно).

Так что, если журналсодержит ссылку на объект сеанса (как кажется, вы делаете), журнал будет уничтожен первым (в пределах, которые я упомянул выше).

Если объекты находятся в переменных пакета, а не в лексическом (my) переменных, и если вы не хотите их изменять, вы можете использовать в основной программе следующее:

use Sub::ScopeFinalizer qw( scope_finalizer );

# Here or wherever.
our $log;
our $session;

{
    # The lexicals within these curlies will get destroyed before
    # global destruction. This will lead to the code ref provided to
    # scope_finalizer getting called before global destruction.

    my $guard = scope_finalizer {
       # The order doesn't matter because the log object 
       # holds a reference to the session object.
       $log     = undef;
       $session = undef;
    };

    # ... Main program here ...
}

Пока журнал содержит ссылку на сеанс, это гарантирует, чтообъекты уничтожаются в правильном порядке. Даже если программа умирает или завершается.


Вы можете определить, сохранились ли объекты класса до глобального уничтожения, добавив в программу следующий код и изучив порядок вывода:

DESTROY { warn($_[0]->id . " destroyed.\n"); }  # In the class

END { warn("Global destruction."); }

  1. В этом посте я свободно использую слово «объект». Я имею в виду не только экземпляры классов, но также скаляры, массивы, хэши, подпрограммы и другие значения и переменные.

    Аналогично, ссылка ссылается не только на ссылку Perl, но и на другие формы ссылок. Например, массивы и хэши ссылаются на свои элементы, а подэлементы ссылаются на захваченные переменные и т. Д.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...