• このエントリーをはてなブックマークに追加


はじめに

以前にこんなエントリを書きました:

Amon2 Web アプリケーションのコントローラ内とかで Data::Section::Simple::get_data_section を呼んで DATA セクションの内容を取得するようなコードを書いたのですが,ここでも,上記のような問題が発生してしまいます.

ので,その修正がてら,一対策方法としてメモしておきます.

サンプルコード

以下の内容は,このコードを基にしています.

事前準備

amon2-setup.pl --flabor=Minimum MyApp として.

サーバを起動しておきます:

carton exec -- perl script/myapp-server 2>/dev/null

アプリケーションに対してそこそこ並行して HTTP リクエストを投げるための簡単なスクリプト sandbox/req.pl:

use 5.18.0;
use warnings;
use utf8;
use Getopt::Long qw( :config posix_default no_ignore_case bundling );
use Time::HiRes ();
use HTTP::Tiny;
 
my %opts = (
    n => 10,
);
GetOptions(
    'n=i' => \$opts{n},
);
 
my $ua = HTTP::Tiny->new();
 
sub main {
    my (%args) = @_;
 
    my $pid0 = $$;
 
    for my $i ( 1 .. $args{n} ) {
        my $pid = fork();
        last if ! $pid;
    }
 
    if ( $$ == $pid0 ) {
        sleep 1;
    }
    else {
        Time::HiRes::sleep ( rand() / 100 );
        my $res = $ua->get( 'http://localhost:5000/' );
        say $res->{status};
    }
}
 
main( %opts );

問題のアプリケーション (branch master

リクエストが来たら DATA セクションを呼見込んでみて,OK なら 200,ダメなら 500 を返す,それだけです:

package MyApp::Web;
use strict;
use warnings;
use utf8;
use parent qw(MyApp Amon2::Web);
use File::Spec;
use Data::Section::Simple qw( get_data_section );
 
sub dispatch {
    my ($c) = @_;
 
    my $foo = get_data_section( 'foo' );
    my $res = $c->create_response( defined $foo ? 200 : 500 );
    $res->content_type( 'text/plain' );
    $res->content( defined $foo ? 'OK' : 'NG' );
 
    return $res;
}
 
1;
__DATA__
 
@@ foo
foobar

このアプリケーションに対して上記のスクリプトを使ってリクエストを投げてみます:

% for i in {1..10}; do echo "**** $i ****"; perl sandbox/req.pl -n 100 | grep 500; done
**** 1 ****
500
500
**** 2 ****
500
**** 3 ****
500
500
500
500
**** 4 ****
500
**** 5 ****
500
500
500
**** 6 ****
500
500
500
500
500
500
**** 7 ****
500
**** 8 ****
500
500
500
500
500
500
500
**** 9 ****
500
500
500
500
500
**** 10 ****
500
500
500

DATA セクションを読み込めていないことがあることがわかります.

とりあえず解決策 (branch solution

こんな考えを基に:

  • コンテキストクラスロード時に,各クラスの DATA セクションをまとめて読み込む
  • コンテキストオブジェクト経由で保持しているデータにアクセスできるようにする
  • あと,できれば,既存のコードをあまり変更したくない

変更内容はこんな感じ:

% g diff lib/MyApp/Web.pm
diff --git a/lib/MyApp/Web.pm b/lib/MyApp/Web.pm
index cd379ef..277192a 100644
--- a/lib/MyApp/Web.pm
+++ b/lib/MyApp/Web.pm
@@ -4,12 +4,35 @@ use warnings;
 use utf8;
 use parent qw(MyApp Amon2::Web);
 use File::Spec;
-use Data::Section::Simple qw( get_data_section );
+use Amon2::Util ();
+use Module::Find;
+use Data::Section::Simple ();
+
+__PACKAGE__->load_data_section();
+
+sub load_data_section {
+    my ($self) = @_;
+    my %data;
+    my @modules = ( __PACKAGE__, useall __PACKAGE__ );
+    for my $m ( @modules ) {
+        my $ds = Data::Section::Simple->new( $m );
+        $data{$m} = $ds->get_data_section();
+    }
+    Amon2::Util::add_method( __PACKAGE__, '__data__', sub { \%data } );
+}
+
+sub get_data_section {
+    my ($self, $key, $pkg) = @_;
+    $pkg ||= caller 0;
+    my $ret;
+    $ret = $self->__data__->{$pkg}{$key}  if $self->can( '__data__' );
+    return $ret;
+}
 
 sub dispatch {
     my ($c) = @_;
 
-    my $foo = get_data_section( 'foo' );
+    my $foo = $c->get_data_section( 'foo' );
     my $res = $c->create_response( defined $foo ? 200 : 500 );
     $res->content_type( 'text/plain' );
     $res->content( defined $foo ? 'OK' : 'NG' );

この変更で,アプリケーションの各所で get_data_section(...) していた部分は,$c->get_data_section(...) と変更するだけでよくなりました.

で,変更後のアプリケーションに対して同リクエストスクリプトを投げてみた結果:

% for i in {1..10}; do echo "**** $i ****"; perl sandbox/req.pl -n 10000 | grep 500; done
**** 1 ****
**** 2 ****
**** 3 ****
**** 4 ****
**** 5 ****
**** 6 ****
**** 7 ****
**** 8 ****
**** 9 ****
**** 10 ****

ちゃんと DATA セクションの内容を取得できているようです.(厳密には直接読み込んでいるわけではありませんが.)

おわりに

本エントリでサンプルとしてあげたのは Minimum flavor ですが,私が実際に修正したアプリケーションは Large flavor,同様の変更で対応できているようです.