うるう秒をテストしたら仮想インタフェースが落ちた話
今月末はうるう秒が予定されていますね。
http://jjy.nict.go.jp/news/leaps2015.html
これについては様々なところで「何が影響を受ける?」「どう対応する?」などが書かれています。
今回はその中でも、「事前に試験環境でうるう秒を入れてみる」ことについてRedHatの記事が書かれていたので、そのお話。
How to clear the Leap Second Insertion flag after it has been received?
この記事の中では leap-a-day.c というコードが公開されていて、簡単に言うと
ということをやってくれます。
これを試験環境で実行したところ、仮想ネットワークインタフェース(IP alias)が落ちたので、再現条件を記録しておきます。
なお、この問題にうるう秒は本質的に関係ありません。
Perlからshared objectの関数を呼び出す
Perlのglobはもうshellを呼ばない
もう10年以上前の話だが、メモ。
IPAのセキュア・プログラミング講座にある4-2. Perl の危険な関数には以下の記載がある。
便利な<> 構文,glob関数であるが,実は内部的にシェルを呼び出している。そのためリスト7の1行目のような文字列を渡した場合,パスワードファイルの内容をメールで送信できてしまう。
が、これはかなーり古いPerlでの話で、pre-5.6.0のみに当て嵌まる話。現在はFile::GlobというXSで記述されたモジュールがよしなにやってくれており、cshを内部でforkすることはない。
このあたりはperldeltaに詳しい。
https://metacpan.org/pod/release/GSAR/perl-5.6.0/pod/perldelta.pod
この記述、加筆修正されないんだろうか…
PerlでGoogle Calendar APIを勉強
アラートのメール通知がきたらGoogle Calendarに記入しておくとあとで振り返るのが簡単なんじゃないかなーと思ったので、まずはお勉強から。
要素
できるようになってから振り返ると、以下の要素を理解する必要があった。
- REST
- OAuth 2.0
- Application Credential
- Authorization Code
- Scope
- Access Token
- Refresh Token
- Google Calendar API 3.0
これからそれぞれの要素について書いていこうと思う。
コードはほぼコピペだが、エラー処理(status=200でないとき)の処理は簡単のため省いている。
PerlでTwitter API SSL化に対応するたった1行
手元でTwitterの古いpostを削除するスクリプトをcron実行してるのですが、Twitter APIってSSLのみ対応になったのをすっかり放置してしまっていました。
cron実行に失敗してメール通知が来たので1週間くらい経ってようやく対応。
Net::Twitterを使っているのでバージョンを上げて、アプリ側では1行追加するだけ。
my $nt = Net::Twitter->new( traits => [qw/OAuth API::RESTv1_1/], consumer_key => $config->{consumer_key}, consumer_secret => $config->{consumer_secret}, access_token => $config->{access_token}, access_token_secret => $config->{access_token_secret}, ssl => 1, # ここを追加 ); if (!defined $nt) { die "Authentication Failed: $!"; }
無事動きました。
Parallel::Fork::BossWorkerAsyncがいい感じ
先日 id:hirose31 と
- 並列処理はParallel::ForkManagerとかParallel::Preforkが定番だけど、もうちょっと効率よくやりたいこともある
- P::ForkManagerはタスクごとにforkするので負荷の分散は綺麗にできるが、タスクの数だけforkが発生して効率がよくない
- P::Preforkだとfork回数は並列度分だけだが、タスク開始前にタスクの分配を完了させないといけない
- Cでmultithreadならmaster-workerモデルでmasterにqueue持ってmutexでロックするような練習問題とかある
- けどPerlでmultithreadやりたくないし、実現するとすればsocket経由でタスクを渡していく感じにしないといけないか
- 誰かCPANに上げてるんじゃないかなー
- なければ作るかー
的な話をしていました。
で、metacpanを漁っていたところ、それっぽいのを見付けました。Parallel::Fork::BossWorkerAsync。
計算モデル
このモジュールは事前に1個のbossとk個のworkerプロセスをforkし、masterが都度タスクを生成して登録していきます。
workerはただただwork_handlerで与えられたsubを実行していき、計算結果をreturnします。
workerの計算結果が帰ってくるとmasterではresult_handlerで与えられたsubが呼び出されます。(たとえば結果を集めるオブジェクトに登録する)
最後にshutdownを呼び出すとbossと全workerが終了します。
プロセスツリーとしてはこうなります。
└─perl,18371 bwa.pl # アプリケーション └─perl,18372 bwa.pl # boss ├─perl,18373 bwa.pl # worker ├─perl,18374 bwa.pl # worker ├─perl,18375 bwa.pl # worker ├─perl,18376 bwa.pl # worker └─perl,18377 bwa.pl # worker
bossプロセスは各workerとアプリケーションとタスク/結果のやりとりをするためにひたすらselect(2)を呼んでいるプロセスです。
workerプロセスはbossからタスクを受け取って処理したらbossに結果を返します。
アプリケーションプロセスはbossとだけ直接通信し、タスクを投げて結果を受け取ります。
利用例
大したものではないですが、podに書かれていないようなモデルでの利用例。
workerは単に1秒sleepし、自分のjob idを返します。
masterは返されたjob idを配列に追加していって最後にdumpします。
それだけ。
この例ではk=5としていて、最初に5プロセスが生まれた後はプロセスの交代は起こっていません。
タスク供給はちょっと不定期な感じにするために、17個ずつ供給し、終了次第次の17個を供給してます。
53 / 17 = 3あまり2
17個のタスクを5並列でやると4秒かかるので、
4 * 3 + 1 = 13秒で実行が完了します。
#!/usr/bin/env perl use strict; use warnings; use Parallel::Fork::BossWorkerAsync; use Data::Dumper qw(Dumper); use Time::HiRes qw(gettimeofday); my @Finished_Jobs = (); my $PARALLELISM = 5; # sleep time my $NUM_WORKS = 53; my $WORKS_AT = 17; # この時点でworkerがforkされた my $bw = Parallel::Fork::BossWorkerAsync->new( work_handler => \&work, result_handler => \&rhandler, worker_count => $PARALLELISM, ); my $thrown_work = 0; while($thrown_work < $NUM_WORKS) { my @tasks; for my $i ($thrown_work .. $thrown_work + $WORKS_AT - 1) { push @tasks, { id => $i, sleep => 1 }; $thrown_work++; if ($thrown_work >= $NUM_WORKS) { last; } } # タスク集合はここでディスパッチされる $bw->add_work(@tasks); # 実行完了待ちタスク数が得られる。この関数はblockしない while($bw->pending) { # "return"されたオブジェクトをそのまま受け取る。以下のように単に呼び出すとblocking my $ref = $bw->get_result; # P::F::BossWorkerAsyncのレイヤでエラーがハンドルされた場合は ERROR というkeyにメッセージが入ってくる(タイムアウトを指定した場合など) if ($ref->{ERROR}) { print STDERR "ERR: ". $ref->{ERROR}; } else { print $ref->{job} . "\n"; } } } # workerプロセスを終了させる $bw->shut_down; print Dumper \@Finished_Jobs; # worker側で各タスクに対してcallbackされる sub work { my ($job) = @_; my $id = $job->{id}; my $t0 = gettimeofday; print "$t0 start $id\n"; sleep $job->{sleep}; my $te = gettimeofday; print "$te end $id\n"; return { job => $id }; } # master側でタスク終了時にcallbackされる sub rhandler { my ($result) = @_; my $job = $result->{job}; push @Finished_Jobs, $job; return $result; }
内部的にData::Dumperを使ったシリアライズをしているらしく、データの区切りに決め打ちのバイト列を指定する必要があります。そこはStorable::nfreeze/thawした方がいいんじゃないかな?と思ったりはします。
P::ForkManagerやP::Preforkに比べると記述は増えますが、用途によっては便利なのでは?
Server::Starterに対応するとはどういうことか
StarletやStarmanと組み合わせてよく使われているServer::Starterですが、普段気にしないような部分を読む機会があったのでメモ。
Server::Starterは --port (TCP) や --path (Unix Domain Socket) を渡すとこれでlisten(2)して起動するworkerに引き渡してくれる。
これはfork(2)とexec(2)によってファイルディスクリプタを引き継ぐことにより実現されているが、ファイルディスクリプタそのものをどのように引き渡しているのか、という問題。
exec(2)により実行バイナリは差し換わってしまうので、プログラム中の変数により引き継ぐことはできない。
Server::Starterではこれを環境変数SERVER_STARTER_PORTにより実現している。
おおよそ以下のような感じ。
FD = integer EQ = '=' SEMICOLON = ';' SERVER_STARTER_PORT = PORT_DESCRIPTION ( SEMICOLON SERVER_STARTER_PORT ) PORT_DESCRIPTION = PORT_NAME EQ FD PORT_NAME = character+
PORT_NAMEはソケットの種類によって違って、TCPであればポート番号(8080)とかホスト名:ポート番号(localhost:8080)、Unix Domain Socketであればそのパス(/path/to/app.sock)となる。
PODにも書いてあるServer::Starter::server_portsはこの環境変数をほどいてPerlのハッシュにして返してくれる小さなサブルーチンとして実装されているので、workerがPerlであればこれを使えばよい。
workerは環境変数から受け取ったファイルディスクリプタの番号をfdopen(3)して(あるいはそれすらもせずに)そのソケットを利用することができる。
ところで別にworkerがPerlであることは要請されていない。
続きを読む