четверг, 19 июня 2008 г.

Perl: Cooking warnings

Данная заметка касается некоторых тактических приемов обработки предупреждений и не претендует на полноценный обзор систем логирования будь-то Log::Dispatch или Log::Log4perl.

Обработка предупреждений

Обычно имея лишь предупреждение подобное этому: "Use of uninitialized value in hash element at ...", - практически не возможно установить, что привело к нему. Необходима дополнительная информация: минимум входящие данные. Поэтому обработку предупреждений можно разделить на два уровня.

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

use CGI::Carp qw(carpout);
open (LOG, ">>/var/log/.../foo.log");
carpout(*LOG);

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

Простейшая реализация обработки предупреждений второго уровня показана ниже.

Подключение обработчика и вызов подпрограммы записи предупреждений:

use Foo::Log;

sub main {
# ...
local SIG{__WARN__} = warn_trap();
# ...
write_warn($id_source, $id_input_data);
}

Непосредственно модуль обработки (без экспорта имен и полезных прагм):

package Foo::Log;

my @warnings = ();

sub warn_trap() {
@warnings = ();
my $old_warn_trap = $SIG{__WARN__};
return sub {
my $warn = shift;
push @warnings, $warn;
if ($old_warn_trap) {
# Конечно, если хотим передать сообщение
# вышестоящему обработчику.
local $SIG{__WARN__} = $old_warn_trap;
eval { &$old_warn_trap($warn) };
}
};
}

sub write_warn(;$$) {
my ($id_source, $id_input_data) = @_;
if (@warnings) {
# Открываем лог файл и
# сохраняем $id_source, $id_input_data и @warnings.
# ...
}
}

Почему не устанавливаю ловушку для __DIE__?
Потому что предпочитаю ловить die в eval и вызывать warn $@, да и первый уровень на страже.

Отладка

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

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

Необходимо получить входящие данные как можно ближе к точке возникновения предупреждения. Такими данными являются аргументы подпрограммы, в которой генерируется предупреждение и, возможно, находиться ошибка. Ведь предупреждение - это сигнал об ошибке либо в логике, либо в данных.

Нет проблем: используем callar и @DB::args в __WARN__ обработчике.

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

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

Более красивым решением может быть тактика точечных обработчиков предупреждений.

Локальный точечный обработчик устанавливается максимально близко к месту возникновения ошибки и ему указывается что необходимо сделать при возникновении предупреждения.

Пример реализации точечного обработчика:

use Data::Dumper;

sub pointed_warn_trap(@) {
my @data = @_;
my $old_warn_trap = $SIG{__WARN__};
return sub {
my $message = join "", shift, Dumper @data;
$old_warn_trap ? eval { &$old_warn_trap($message) } :
warn $message;
};
}

Пример использования точечного обработчика:

local $SIG{__WARN__} = pointed_warn_trap($foo, $too);

В этом примере $foo может быть обрабатываемыми данными, а $too - информацией из базы.

После того как разместили точечный обработчик остается только, подобно рыбаку, набраться немного терпения и словить "рыбку".

Заключение

Эти приемы обработки предупреждений хоть и просты, но очень помогают отлавливать ошибки и сделать код более качественным.

P.S.
Копия: http://kiev.pm.org/node/188.

вторник, 27 мая 2008 г.

Один нюанс использования map

Когда блок map должен вернуть ссылку на анонимный хеш и этот блок содержит более чем одну логическую строку, то необходимо подсказать, что фигурные скобки являются обозначением анонимного хеша, а не блока лексической области видимости. В качестве подсказки могут выступать круглые скобки, в которые обрамляется анонимный хеш.

Примеры.

Простой блок - perl понимает сам, что имеет дело с анонимным хешем:

> perl -MData::Dumper -e 'print Dumper [map { { 1 .. 4 } } (1 .. 2)]'
$VAR1 = [
{
'1' => 2,
'3' => 4
},
{
'1' => 2,
'3' => 4
}
];

Сложный блок - perl думает, что имеет дело с лексической областью видимости:

> perl -MData::Dumper -e 'print Dumper [map { 1; { 1 .. 4 } } (1 .. 2)]'
$VAR1 = [
1,
2,
3,
4,
1,
2,
3,
4
];

Подсказка про анонимный хеш при помощи круглых скобок:

> perl -MData::Dumper -e 'print Dumper [map { 1; ({ 1 .. 4 }) } (1 .. 2)]'
$VAR1 = [
{
'1' => 2,
'3' => 4
},
{
'1' => 2,
'3' => 4
}
];

Кстати, когда анонимный хеш возвращается из обычной подпрограммы при помощи return, то нет нужды в подсказке в виде круглых скобок, так как return устраняет вышеописанную неопределенность. А вот если return отсутствует и подпрограмма возвращает последние вычисленное значение, то подсказка необходима. В блоке map return использовать нельзя.

http://kiev.pm.org/node/182

пятница, 22 февраля 2008 г.

Осторожно, Perl!

Шуточная на половину статья, в которой упоминаются некоторые перловые хаки. Perl самый опасный язык программирования, поскольку он развивает "отвратительные" качества в людях. Некоторые их называют "добродетелями" программистов: лень, нетерпение, высокомерие (laziness, impatience, hubris).

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

Вот собственно статья.

Среди множества языков программирования один занимает особое место. Это язык Perl! Если вы его не знает и получите предложения его освоить - бегите как можно дальше, иначе...

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

Причем это происходит постепенно и от того более коварным является этот процесс. Человек этого не замечает и даже потом, когда Perl полностью им завладевает, человек не будет это осознавать. Более того, он будет даже превозносить эти отвратительные черты, как великие добродетели программистов. Но какие это же добродетели!

Возьмем, например, высокомерие. Оно, якобы, помогает писать замечательные программы. Глупости все это. Высокомерие Perl программистов настолько велико, что они даже встревают в спор С++ и OCaml программистов о производительности их языков. Сам был очевидцем такого спора. В споре шел разговор как оптимально генерируется ассемблерный код, а также затрагивались вопросы оптимизации хвостовой рекурсии... Решили провести тесты, например, посредством вычисления чисел Фибоначчи.

А тут мимо проходил Perl программист. И что вы себе думает, он посмел высокомерно утверждать, что вычислит на Perl числа Фибоначчи быстрей чем они. Поспорили на бутылку коньяка. Программисты C++ и Ocaml знали, что из себя представляет Perl, поэтому сразу согласились на спор. Но они не знали, что из себя представляют Perl программисты. Кто бы мог подумать, что этот Perl программист выиграет спор! Он первый вычислил числа Фибоначчи! Когда остальные участники спора, отказываясь верить словам, подошли увидеть все своими глазами, Perl программист запустил на исполнение следующий код:

use strict;
use warnings;
use Memoize;
memoize('fib');

sub fib {
my $n = shift;
return $n if $n < 2;
fib($n-1) + fib($n-2);
}

my $n = $ARGV[0];
print "fin($n) = ", fib($n), "\n";

Конечно, для тех, кто знаком с модулем Memoize, все становиться ясно с первого взгляда.

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

С дрогой стороны, Perl не только "самый быстрый язык", но и самый ленивый. Нет, не надо говорить, что самый ленивый - это Haskell. Вы бы посмотрели, как иногда Haskell программисты борются с ленивостью своего языка. Perl ленив по другому. Правильно, он ленив, потому, что ленивы его использующие люди. Кстати, и потому что они ленивы, они даже не нагружают свой компьютер лишней работой: вспомните про модуль Memoize. А ленивые списки и прочие есть и у Perl, например: Data::Lazy, Scalar::Defer, Object::Lazy, Tie::Scalar, Tie::StdScalar и даже DBIx::LazyMethod.

Perl также славиться тем, что "делает за вас вашу работу". Вроде-бы это очень хорошо, но постойте, это же самая большая "медвежья услуга". Со временем вы обленитесь и ваша лень не будет иметь границ.

Ну а поскольку Perl очень быстр и ленив, то он делает людей его использующих очень нетерпеливыми.
Быстро сделав свою работу, вы будете с нетерпением ждать, когда, например, программисты, реализующие клиентскую часть проекта, напишут и отладят свой код. Вы будите скучать от безделья, нервничать от осознания того, что вас сдерживают и проект движется черепашьими темпами. Придется пить валерьянку.

Многие не в силах выдержать это, ломаются - бреют голову, посыпают ее пеплом и уходят в монастырь (http://perlmonks.org). Там они в спокойной обстановке, вдалеке от суетного мира занимаются каллиграфией, то есть обфускацией (http://www.perlmonks.org/index.pl?node=Obfuscated%20Code).

Лень привела также к тому, что Perl является сосредоточением множество закорлючер и заклинаний. (Про семейство APL языков скромно умолчу.) И все это преподноситься под соусом "cуществует более одного способа сделать это". Да ни один силач не поднимет книгу Perl рецептов.

А посмотрите, что Perl программисты сделали с совершенной и строгой концепцией Объектно-Ориентированного программирования!
Ну разве это объекты? Правда Damian Conway (Tie::SecureHash, Class::Contract) пытался образумить Perl программистов, да что толку. Как-бы в ответ, появились модули, позволяющие делать всевозможные хаки, залезать в чужие области видимости и менять значения локальных структур! Ужас! (Перечень модулей, опасных для неокрепших духом, вырезан цензором.) Хотя, надо отдать должное, на основе этих идей появилась реализация Аспектно-Ориентированного программирования на Perl (Aspect).

Так что, программисты, берегитесь Perl - это очень опасный язык! Когда Perl проникнет в ваши умы, вы никогда больше не сможете нормально писать на других языках программирования. И этому также будет способствовать то, что Perl из всех языков программирования дальше всего находиться от компьютера и ближе всего к человеку. Не дай Бой, вы из программиста станете писателем или поэтом. А ведь были уже прецеденты, например, королева поэзии Perl Sharon Hopkins.

С другой стороны, чтобы стать настоящим мастером, надо пройти через все это. Но берегись.
Perl - это как обоюдоострый меч, которым можно делать чудеса, а можно серьезно пораниться.

Вы спросите, как же мне, использующему Perl, удалось избежать его пагубного влияния?
А у меня иммунитет - я же не программист. Я химик! :-

Коментарии и пожелания оставляте в http://kiev.pm.org/?q=node/157

среда, 6 февраля 2008 г.

Perl JSON модули с поддержкой UTF-8

Введение

Статья представляет обзор perl модулей для работы с JSON в контексте UTF-8. Подразумевается работа с UTF-8 на уровне символов, а не байтов. Если нужна работа на уровне байт, то подойдет любой из нижеперечисленных моделей, который устроит по скорости.

На CPAN существуют следующие модули:

  • JSON - parse and convert to JSON (JavaScript Object Notation).

  • JSON::XS - JSON serialising/deserialising, done correctly and fast

  • JSON::Syck - JSON is YAML

  • JSON::PC - fast JSON Parser and Converter (JSON::PC is a XS version of JSON).

  • JSON::DWIW - JSON converter that Does What I Want

В качестве тестовых данных будем использовать следующий JSON:

 { "hello":"Hello, \u00ab\u043f\u0440\u0438\u0432\u0435\u0442\u00bb \u2116 \u263a. Good by." }

В JSON содержится фраза ``Hello, <<привет>> #. Good by.'', в которой кавычки, номер и смайлик ``уникодные''. Именно как \uxxxx в JSON кодируются UTF-8 символы (http://json.org).

Этому JSON соответствует следующая Perl структура:

 { 'hello' => "Hello, \x{ab}\x{43f}\x{440}\x{438}\x{432}\x{435}\x{442}\x{bb} \x{2116} \x{263a}. Good by." }

Ну что-ж теперь приступим к тесту моделей.

Обзор модулей

JSON.

Для работы у UTF-8 в конструкторе необходимо это указать:

 JSON->new(utf8 => 1);

Модуль корректно ставит UTF-8 флаги, и обратном преобразовании кодирует UTF-8 символы. Но есть большое но: модель споткнулся на символах кавычки елочкой.

JSON::PC.

Как указано в документации на модуль, JSON::PC является XS версией модуля JSON, и как истинный приемник наследует туже проблему с русскими или французскими кавычками.

Модуль сам разбирается где UTF-8, ничего явно указывать не нужно.

JSON::Syck.

Модуль JSON::Syck написан сообразительной китаянкой, которая заметила что JSON по сути является YAML, и просто использовала библиотеку libsyck.

UTF-8 не поддерживается вообще, модель ориентирован на работу с октетами, о чем честно написано в документации. Подразумевается \uxxxx, а не ImplicitUnicode опция.

JSON::XS.

А вот модуль JSON::XS показал себя достойно со всех сторон. Правда перед преобразованием из JSON в Perl надо указать флаг utf8, а при обратном преобразовании - флаг ascii.

Из JSON в Perl:

 use JSON::XS;
my $json_xs = JSON::XS->new();
$json_xs->utf8(1);
$json_xs->decode($json_data);

Из Perl в JSON:

 use JSON::XS;
my $json_xs = JSON::XS->new();
$json_xs->ascii(1);
$json_xs->encode($perl_data);

Можно также задать флаг pretty для красивого форматирования результирующего JSON.

JSON::DWIW.

Последний модуль JSON::DWIW также ведет себе отлично, необходимо лишь в конструкторе сообщить об UTF-8:

 use JSON::DWIW;
my $json_dwiw = JSON::DWIW->new({ escape_multi_byte => 1 });
$json_dwiw->from_json($json_data);
$json_dwiw->to_json($perl_data);

Бонусы

Два пакета JSON::XS и JSON::DWIW нормально обрабатывают ситуацию, когда передаваемая им JSON строка не является последовательностью октетов, а является perl строкой с UTF-8 - не надо делать лишнюю проверку.

Benchmark

До этапа тестирования производительности успешно добрались лишь два модуля: JSON::XS и JSON::DWIW, - к которым нет ни одной претензии.

Собственно тест скорости на не слишком большой структуре:

 use Benchmark qw(cmpthese);
 cmpthese(10000, {
'JSON::XS' => sub {
my $json_xs = JSON::XS->new();
$json_xs->utf8(1);
$json_xs->decode($json_data_u);
$json_xs->ascii(1);
$json_xs->encode($perl_data_expected)
},
'JSON::DWIW' => sub {
my $json_obj = JSON::DWIW->new({ escape_multi_byte => 1 });
$json_obj->from_json($json_data_u);
$json_obj->to_json($perl_data_expected)
},
});

А вот и результаты:

               Rate JSON::DWIW   JSON::XS
JSON::DWIW 5246/s -- -78%
JSON::XS 24151/s 360% --

Вывод

Для меня вывод однозначен --- JSON::XS. Модуль ведет себя отлично с UTF-8, корректно преобразовывает данные в Perl строки и к тому же обладает приличной скоростью.

P.S. Для комментарий - http://kiev.pm.org/?q=node/153

понедельник, 28 января 2008 г.

Объекты? Зачем?! Точнее, не всегда. Замыкания!

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

Цель заметки --- показать, что иногда проще обойтись без объектов, а использовать замыкания.

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

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

package Foo;

sub new {
my $class = shift;
my $foo = shift;
return bless \$foo;
}

sub foo {
my $self = shift;
$$self += shift;
print $$self, "\n";
}

1;

Пример использования:

use Foo;
my $foo = Foo->new(3);
$foo->foo(1);
$foo->foo(1);
$foo->foo(2);
$foo->foo(2);

Теперь сделаем аналогичный по функционалу "сумматор" с использованием замыкания:

sub make_foo {
my $foo = shift;
return sub {
$foo += shift;
print $foo, "\n";
};
}

Пример использования:

my $foo = make_foo(3);
$foo->(1);
$foo->(1);
$foo->(2);
$foo->(2);

Как видим вариант с замыканием проще, компактней и элегантней.

Пожалуй на этом и остановимся --- читатель дальше сможет сам развить тему. Приведу лишь цитату Стива Маджевски: "Объект --- это совокупность данных вместе с привязанными к ним процедурами... Замыкание --- это процедура вместе с привязанной к ней совокупностью данных."


P.S.
Кстати, Parrot оперирует объектами (PMC),
но при вызовах подпрограмм использует стиль передачи продолжений, который не мыслим без замыканий.

P.P.S
Копия и комментарии находятся на kiev.pm.org

пятница, 7 декабря 2007 г.

Отцы и дети или perl, fork и деструкторы

Я конечно не Тургенев Иван Сергеевич, но также кое-что попробую рассказать о взаимоотношениях отцов и детей. Рассказать не все, а лишь то, что Иван Сергеевич умышленно опустил, чтобы не уменьшать художественную ценности своего произведения излишними техническими подробностями. Скажу сразу: поскольку эта история произошла давным-давно, то некоторые детали потеряны или, наоборот, обросли некоторыми выдумками и преувеличениями, так что за 100% достоверность не ручаюсь.

А началась все тогда, когда при открытом соединении с базой данных процесс вызвал системный вызов fork, а затем с удивлением обнаружил, что база "упала". Пришлось ему звать на помощь. Вариант с базой проверили сразу: база как работала, так и продолжала работать. Пожурили процесс за ложные показания, и начали копаться в нем.

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

Но как обычно, большинство дорожек уже протоптаны, и нужно лишь найти правильную. Посмотрели документацию на модуль DBI и увидели атрибут InactiveDestroy, который говорит, что не надо уничтожать соединение с базой, если оно не твое.

Но как быть, когда пойдем путями неведанными, дорогами не хожеными? Стали искать решение и для такого случая, когда в сторонней библиотеке нет такого замечательного атрибута как InactiveDestroy.

Как научиться управлять деструкторами? Искали, искали и наконец-то нашли в модуле POSIX вызов _exit. Этот вызов говорит: "а пошел-ка, то на ..., то есть убирай за собой сам!" Так это то, что нужно! Конечно прямо этому совету никто не последовал - кому самому все убирать охота. Обернули, то что необходимо прибрать в лексическую область видимости - пусть сборщик мусора убирается там, а после этой области прописали POSIX::_exit(0).

На это можно было бы ставить точку. Все работает отлично, да и на вооружении теперь еще один трюк. Но интуиция подсказывала, что это только начало. Что будет, если дочерний процесс сам начнет использовать дескриптор к базе данных? Конечно так делать очень не хорошо. Но вдруг как-то явно или не явно это произойдет. Поэтому надо выработать простые правила, чтобы такой ситуации можно было бы легко избежать, то есть надо по полочкам разложить взаимоотношения "отцов и детей".

Как оказалось эти взаимоотношения можно разделить на два варианта: когда дочерний процесс будет существовать дольше своего родителя и когда дочерний процесс, cделав свою работу, завершиться раньше родительского.

Второй вариант был уже описан выше и для него достаточно использовать POSIX::_exit(0), если, конечно нет InactiveDestroy. В первом же варианте надо в дочернем процессе сразу же создать копии необходимых данных из родительского.

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

Когда отец отпускает своего ребенка в самостоятельное плавание, он его наставляет: "сынок, воспользуйся моими трудами вовремя, создай на их основе как можно быстрей свой капитал и стань скорей независимым".

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

Ну вот и все. Еще одна страница в отношении "отцов и детей" стала более ясной. По крайней мере в мире информационных технологий, а вот в жизни все намного сложней и одновременно проще!

четверг, 6 декабря 2007 г.

FastCGI, mod_perl и прочие

С января 2001 года сфера моей профессиональной деятельности тесно связна с созданием web-сервисов, в основном аналитических.

Одной из технологий, используемых в работе, является mod_perl. FastCGI не использовался ни разу. Почему? Как часто отвечают: "по историческим причинам". А вот сейчас решил посмотреть в сторону FastCGI подробней. Специфика нового проекта подразумевает наличие frontend'да, например, nignx. "Нет, проблем", - говорю сам себе: "frontend'ом будет nignx, а backend'ом - Apache". Но не так все просто.

В рассылке по nignx, до того как было все разложено по полочкам в различных документациях и заметках, часто задавались вопросы по использованию PHP в режиме FastCGI. То есть люди массово избавлялись от Apache mod_php, переходя не FastCGI. При этом, что в тот момент, не знаю как сейчас, патч php-fpm (http://php-fpm.anight.org/) не был включен в официальные исходники, так что многие пользовалось spawn-fcgi от lighttpd.

Что это? Дань моде или нечто большее? Тем не-менее этот процесс подтолкнул меня посмотреть подробней, какие есть у FastCGI преимущества. Да, я знаю, mod_perl и mod_php координально отличаются в области загрузки кода, но будем считать, что php акселераторы работают хорошо и это не является причиной перехода с mod_php на FastCGI.

Известно, что процессы Apache с mod_perl очень прожорливы на память. Может с FastCGI ее требуется меньше? Теоретически разница не должна быть слишком большой: ведь под mod_perl можно все используемые модули загрузить до начала создания дочерних процессов.

Стоп! Если мне не изменяет память, то mod_php так не может! Наверно это и есть причина перехода с mod_php на FastCGI. Хм, но ведь и spawn-fcgi в этом вопросе не помощник...

Тем не-менее с FastCGI решил разобраться до конца. Прописал в конфиге nignx какой порт слушать и занялся perl. Для perl существует три основных модуля FCGI, FCGI::ProcManager и FCGI::Spawn. (о FCGI::Async и других разговор не ведем). Первый является основным, второй - это менеджер процессов. Третий является надстройкой над первым двумя и предназначен для запуска скриптов через require, то есть является в некотором смысле аналогом Apache::PerlRun, поэтому мы его не рассматриваем.

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

use FCGI;
use CGI;

my $socket = FCGI::OpenSocket(":9000", 5);
my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV, $socket);

my $count = 0;

while($request->Accept() >= 0) {
$count++;
print <<TEXT;
Content-Type: text/html

<h1>hello</h1>
<p>$count</p>
<hr>
TEXT

print "$_ = $ENV{$_}<br>\n" foreach sort keys %ENV;

print "<hr>\n";

my $query = CGI->new();
print "$_ = ", $query->param($_), "<br>\n" foreach sort $query->param();

}

FCGI::CloseSocket($socket);

Но это пример имеет большой недостаток: всего лишь один процесс FastCGI.
На помощь приходит модуль FCGI::ProcManager, который создает до основного цикла
заданное количество потомков, выполняющих всю полезную работу по обработке запроса:

use FCGI;
use FCGI::ProcManager;
use CGI;

my $proc_manager = FCGI::ProcManager->new({ n_processes => 10 });

my $socket = FCGI::OpenSocket(":9000", 5);

my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV, $socket);

$proc_manager->pm_manage();

my $count = 0;
while($request->Accept() >= 0) {
$proc_manager->pm_pre_dispatch();
$count++;
print <<TEXT;
Content-Type: text/html

<h1>hello</h1>
<p>$count</p>
<hr>
TEXT

print "$_ = $ENV{$_}<br>\n" foreach sort keys %ENV;

print "<hr>\n";

my $query = CGI->new();
print "$_ = ", $query->param($_), "<br>\n"
foreach sort $query->param();

$proc_manager->pm_post_dispatch();
}

FCGI::CloseSocket($socket);

Кстати, если кто знает ответе: в линуксе до сих про плохо с большим количеством
прослушивающих один сокет процессов и проходится перед аccept делать монопольную
блокировку файла "регулировщика"?

Вернемся к основной теме. В примерах использования FCGI::ProcManager используется модуль CGI::Fast, используя которых вышеприведенный вариант можно привести к следующему виду:

BEGIN {
$ENV{FCGI_SOCKET_PATH} = ":9000";
$ENV{FCGI_LISTEN_QUEUE} = 5;
}

use CGI::Fast;
use FCGI::ProcManager;

my $proc_manager = FCGI::ProcManager->new({ n_processes => 10 });

$proc_manager->pm_manage();

my $count = 0;

while(my $query = CGI::Fast->new()) {
$proc_manager->pm_pre_dispatch();

$count++;
print <<TEXT;
Content-Type: text/html

<h1>hello</h1>
<p>$count</p>
<hr>
TEXT

print "$_ = $ENV{$_}<br>\n" foreach sort keys %ENV;

print "<hr>\n";

print "$_ = ", $query->param($_), "<br>\n"
foreach sort $query->param();

$proc_manager->pm_post_dispatch();
}

Теперь рассмотрим фрейморки Catalyst и CGI::Application.

Для Catalyst создаем новый проект

catalyst.pl MyApp

и запускаем

myapp_fastcgi.pl -listen=localhost:9000 -nproc=10

Все просто. А вот с CGI::Application немного сложней. Модуль CGI::Application::FastCGI, как оказалось, предназначен для работы из-под "пускалки", поэтому используем не его, а непосредственно FCGI::ProcManager.

BEGIN {
$ENV{FCGI_SOCKET_PATH} = ":9000";
$ENV{FCGI_LISTEN_QUEUE} = 5;
}

use WebApp;
use CGI::Fast;
use FCGI::ProcManager;

my $proc_manager = FCGI::ProcManager->new({ n_processes => 10 });
$proc_manager->pm_manage();

while(my $query = CGI::Fast->new()) {
$proc_manager->pm_pre_dispatch();
my $app = WebApp->new(QUERY => $query);
$app->run();
$proc_manager->pm_post_dispatch();
}

Ну вот, с запуском разобрались, теперь посмотрим на использование памяти в реальных условиях. Как и ожидалось выигрыша по памяти почти нет, разумеется, когда Apache собран без излишеств.

Так что за nignx можно смело ставить либо mod_perl, либо FastCGI. Это когда один скрипт. А когда скриптов несколько и они используют множество общих модулей, то выигрыш варианта с mod_perl очевиден.

Ну вот пожалуй и все.

P.S. Если я где-то ошибся в рассуждения, просьба поправить.

20 декабря 2007, Николай.

P.P.S
Первоначально опубликовано на http://kiev.pm.org/?q=node/111.
Дату оставил как есть. :-)
В perl коде добавил вызовы pm_pre_dispatch pm_post_dispatch, которые для краткости примеров первоначально удалил.
По адресу http://kiev.pm.org/?q=node/111 смотрите ценные замечания, так же оставляйте свои.