Какой безопасный способ использовать 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 по мере необходимости, поэтому остальная часть приложения не должна заботиться о деталях. Сохраняйте код инкапсулированным и хорошо отделенным от других частей системы.
- Не используйте Apache:: DBI. Я настоятельно рекомендую DBIx:: Connector, который при необходимости открывает новое соединение и не сохраняет плохое поведение ни простого DBI, ни Apache:: DBI: см. http://search.cpan.org/~dwheeler/DBIx-Connector-0.32/lib/DBIx/Connector.pm#Description для подробного описания того, как он отличается.
Я использую 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; хотя он может позаботиться об этом за вас).