Pull to refresh

Общение с fastcgi менеджером

Reading time3 min
Views2.9K

Описание


Маленькое расширение для FCGI::ProcManager, позволяющее обращаться к менеджеру fcgi процессов. Для связи сторонней программы с менеджером используется сокет.

Подводные камни


Модуль FCGI::ProcManager используется для порождения обработчиков входящих запросов. Текущий процесс является менеджером. Со старта он порождает обработчиков (n_processes штук), далее он поддерживает их количество, следя за погибшими в бою. Для этих целей он использует wait. Тут и кроется проблемка. После того, как запущены потоки, менеджер, вызывая wait, блокируется. Достучаться до него можно только через сигналы. Исполнять в обработчике сигнала код нужно с умом и аккуратно, гонять там говнокод — нехорошо. А значит необходимо наладить другой канал связи.


Реализация


Для примера попробуем заставить менеджера слушать сокет. Для этого нам надо заставить менеджера не блокироваться. За ожидание в FCGI::ProcManager отвечает функция pm_wait:
sub pm_wait {
  my ($this) = self_or_default(@_);

  # wait for the next server to die.
  next if (my $pid = wait()) < 0;

  # notify when one of our servers have died.
  delete $this->{PIDS}->{$pid} and
    $this->pm_notify("server (pid $pid) exited with status $?");

  return $pid;
}


Нужно заменить вызов wait на waitpid и вставить туда наш код. Наш менеджер должен открыть и слушать еще один порт, и в режиме ожидания периодически смотреть за потомками и слушать порт. Напишем свой вариант функции pm_wait и подменим одноименную функцию в модуле FCGI::ProcManager с помощью monkey patch. В качестве реакции на сообщения будем перегружать всех потомков, предварительно обновив бибилиотеки проекта.

use FCGI;
use FCGI::ProcManager;
use IO::Select;
use IO::Socket;
use POSIX ":sys_wait_h";
#use Module::Refresh;
use feature ':5.14';
use strict;

# For fastcgi app
my $socket = FCGI::OpenSocket( ":9019", 5 );
my $request = FCGI::Request( \*STDIN, \*STDOUT, \*STDERR, \%ENV, $socket, FCGI::FAIL_ACCEPT_ON_INTR );

# To communicate with the manager
my $server = IO::Socket::INET->new(LocalPort => '9034',
                                   Type => SOCK_STREAM,
                                   Reuse => 1,
                                   Listen => 1)
    or die "Couldn't start messange server $@\n";
my $select = IO::Select->new($server);

# Patch
my $pm_wait = sub {
	my ($pm) = @_;
	my $pid;
	while (1) {
		last if ($pid = waitpid(-1, WNOHANG));
		
		foreach my $client ($select->can_read(1)) {
			if ($client == $server) {
				$client = $server->accept();
				$select->add($client);
			}
			else {
				my $msg = <$client>;
				chomp $msg;
				if ($msg eq 'reload') {
					#my $refresher = Module::Refresh->new();
					#my @module_list = qw( MyLib1.pm  My/Lib2.pm );
					#foreach my $module_name (@module_list){
					#	$refresher->refresh_module($module_name);
					#}
					$pm->sig_manager('HUP');
				}
				print $client "done!\n";
				$select->remove($client);
				close $client;
			}
		} 
	}
	# notify when one of our servers have died.
	delete $pm->{PIDS}->{$pid} and $pm->pm_notify("server (pid $pid) exited with status $?");
	return $pid;
};

no strict 'refs';
*{'FCGI::ProcManager::pm_wait'} = $pm_wait;
use strict 'refs';


my $pm = FCGI::ProcManager->new(  );
$pm->pm_manage(	n_processes => 3, pm_title => 'perl-fcgi-pm', die_timeout => 10 );

while ( $request->Accept() >= 0 ) {
		$pm->pm_pre_dispatch();
        print "Content-Type: text/plain\n\n";
		print "server works";
        $pm->pm_post_dispatch();
}
FCGI::CloseSocket($socket);

close $server;


Следом код для теста:
use IO::Socket;
use feature ':5.14';
use strict;

my $socket = new IO::Socket::INET(Proto => "tcp",
                                 Type  => SOCK_STREAM,
                                 PeerPort => 9034,
                                 PeerAddr => "127.0.0.1" )
    or die "Can't connect: $!";

#my $msg = $ARGV[0];
my $msg = 'reload';

if ($msg) {
    print $socket "$msg\n";
    my $answer = <$socket>;
    say "Answer: ".$answer;
}


Итого


Таким образом мы имеем возможность посылать сообщения (протокол можно придумать самому) менеджеру fastcgi процессов. Это можно использовать для опроса текущей конфигурации приложения. Перезагрузить бибилиотеки проекта (закомментировано в примере). Можно увеличить/уменьшенить количество обработчиков
$pm->{n_processes} = 10;
$pm->sig_manager('HUP');

Ну и такие мелочи как включение/отключение профилирования. Само собой эти задачи можно решить другими способами.

There's more than one way to do it
Tags:
Hubs:
Total votes 14: ↑14 and ↓0+14
Comments5

Articles