Какой безопасный способ использовать fork с Apache::DBI под mod_perl2?

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

DBD::mysql::db selectall_arrayref завершилась неудачно: команды не синхронизированы; Вы не можете запустить эту команду сейчас в /usr/local/www/apache22/data/test-fork.cgi строка 20.

Переподключение не помогает, так как Apache::DBI переподключается во всех процессах, как я понял в следующей ошибке

Сервер обнаружил внутреннюю ошибку и не смог выполнить ваш запрос.

Сообщение об ошибке: драйвер DBD не реализовал атрибут AutoCommit в /usr/local/lib/perl5/site_perl/5.8.9/Apache/DBI.pm строка 283.,

Вот код источника:

use Data::Dumper 'Dumper';
use DBI ();

my $dbh = DBI->connect($dsn, $username, $password, {
        RaiseError => 1,
        PrintError => 0,
    });
my $file = "/tmp/test-fork.tmp";

my $pid = fork;
defined $pid or die "fork: $!";

if ($pid) {
    my $rows = eval { $dbh->selectall_arrayref('SELECT SLEEP(1)') };

    print "Content-Type: text/plain\n\n";
    print $rows ? "parent: " . Dumper($rows) : $@;
}
else {
    my $rows = eval { $dbh->selectall_arrayref('SELECT SLEEP(1)') };

    open FH, '>', $file or die "$file: $!";
    print FH $rows ? "child: " . Dumper($rows) : $@;
    close FH;
}

Код, который я использовал для переподключения:

...
else {
    $dbh->disconnect;
    $dbh = DBI->connect($dsn, $username, $password, $attrs);
    my $rows = eval { $dbh->selectall_arrayref('SELECT SLEEP(1)') };

    open FH, '>', $file or die "$file: $!";
    print FH $rows ? "child: " . Dumper($rows) : $@;
    close FH;
}

Есть ли безопасный способ использовать Apache::DBI с разветвлением? Есть ли способ заставить его создать новое соединение, возможно?

2 ответа

Решение

Не раскошеливайтесь под mod_perl2. Используйте Apache2:: Subprocess. Смотрите также Это плохая идея, чтобы форк под mod_perl2?

Я вижу несколько вариантов:

  • Явно закройте ваши дескрипторы БД при форке и откройте их при необходимости

например:

my $dbh = DBI->connect(...);

my $pid = fork;
defined $pid or die "fork: $!";

if ($pid) {
    # parent...
}
else {
    # child...
    undef $dbh;

Это можно сделать проще, сохранив $dbh в объекте и передавая этот объект по мере необходимости в части вашей системы. Объект будет отвечать за повторное открытие $ dbh по мере необходимости, поэтому остальная часть приложения не должна заботиться о деталях. Сохраняйте код инкапсулированным и хорошо отделенным от других частей системы.

Я использую DBIx:: Connector в моей системе внутри объекта Moose, который использует делегирование метода для предоставления dbh. Приложение просто делает:

my $dbh = $db_dbj->dbh;
my $sth = $dbh->prepare(...);
# more boring DBI code here

... И DBH повторно подключается / восстанавливается по мере необходимости, невидимо.


Кроме того, вы должны быть очень осторожны с использованием простых файловых дескрипторов в многопроцессорной среде. Вы могли бы очень легко забить ваши данные. open (my $fh, $file) or die "Cannot open $file: $!" гораздо безопаснее.

Я также немного нервничаю, увидев, что вы используете eval {} блоки без проверки содержимого $@, Вы просто маскируете ошибки вместо того, чтобы иметь дело с ними, поэтому может происходить больше вещей, чем вы знаете. Проверьте значения результата (или лучше, используйте явный модуль обработки исключений, такой как Try:: Tiny. Use use strict; use warnings;,

PS. Я просто заметил, что вы явно включаете DBI в вашем коде. Не делай этого. Если вы используете Apache:: DBI в вашем файле startup_modperl.pl (или как вы называете файл начальной загрузки), вам никогда не придется включать сам DBI. Я не могу сказать наверняка, но я не уверен, что правильный пакет вызывается (прошло много времени с тех пор, как я посмотрел на внутренности Apache:: DBI; хотя он может позаботиться об этом за вас).

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