隠居日録

2016年(世にいう平成28年)、発作的に会社を辞め、隠居生活に入る。日々を読書と散歩に費やす

Perlでメモリリーク?

多分2016年の12月ぐらいからPOPFileを使っていて、今まで特に運用上の問題はなかった。最近ふと気づいたのだが、どんな単語が登録されているのか見てみたら、文字化けしているものや、意味のない英数字の羅列なども登録されていて、これをきれいにする方法はないかと色々検索してみた。そのものずばりのツールはなかった。ただし、はてなダイアリーに以下のエントリーがあり、この方法でも一部は削除できるだろうと思って試してみた。

b.hatena.ne.jp

popfileを終了し、popfile.dbのコピーを取り、sqlite3のコマンドを実行し、popfileを再起動した。特にエラーもなく立ち上がり、その後メールの分類もしていたので、問題はないだろうと思っていたのだが、いつのまにかメールの分類が止まっており、web UIからアクセスすると、popfileが停止しているようだった。
/var/log/messagesを見ると、

swap_pager_getswapspace(12): failed

のようなウォーニングが大量に出ていて、最終的にはswapが枯渇して、終了していた。これはpopfile.dbが壊れたのかと思い、バックアップしておいたものに差し替えて再起動してみたが、それでも同じようにswapが枯渇する。FreeBSDに移行する前も、移行した後も特に問題なく動いていたと思っていたのだが、いったい何が起きているのはわけがわからなくなってしまった。でもswapinfoでswapの残り状況を見てみると、結構な速度で少なくなっていっている。それで、一分毎にswapinfoとtopで表示される、SIZEとRESの値をファイルに書き出して、調べてみた。

f:id:prozorec:20190318115945p:plain
メモリ使用状況

水色のSIZEの縦軸は右側の目盛りになるのだが、2時間ぐらいで61Mから675Mになり、SWAPの使用量はは12%から45%になった。これはどう考えてもperl内でメモリーリークが起きているのでは?だが、なんでpopfile.dbを編集するまで気づかなかったのだろう?ログインして、手動で起動したときだけこの問題が発生しているような気がする。となると、何らかの環境変数が影響しているのだろうか?

それで、環境変数をunsetしながら試してみると

LANG=ja_JP.UTF-8

が設定されていると、モリモリとメモリを消費するように見える。LANGをunsetして起動した場合は、こんな感じになる。

f:id:prozorec:20190318120911p:plain
メモリ使用量2

8時間ほど動かしてもさほど上昇しない。ただ、全く上昇しないわけではなく少しは増えているので、やっぱり漏れているのだろうか?よく判らない。

ちなみにFreeBSD 12のperlはv5.28.1で最新のはず。

perlで日本語文字化けが発生し、はまった理由と対策

perlで日本語の文字化けが起きてしまい、はまってしまった。よく考えてみたら、前に一度同じ理由ではまったことがあり(その時はSQLiteに書き込む際の値が文字化けした)、自戒を込めて記録しておく。

よく、perlに関する説明で、「日本語を処理するときは、(内部コードになっていない場合は)decodeで内部コードに変換し、最終的なところでencodeで所望の漢字コードに変換すると文字化けが起きない」というようなことが書かれている。たしかに、この説明は正しい。見た目においては、この方法で問題はほぼ発生しないと思う。ところが、以下のようなperlスクリプトを実行すると、$tagの値は「title」であり、$titleの値は「日本語文字列」なのだが、それらをechoコマンドで表示させると、文字化けする。

use Encode;
use utf8  ;
my $v = '<title>日本語文字列</title>' ;

my ($tag, $title) = $v =~ m{\A<([A-Za-z]+?)>(.*?)</[A-Za-z]+?>};

$title = encode ('utf8', $title);

print "tag = $tag\n";
print "title = $title\n";

my $cmd = "echo $tag $title";
system ($cmd);

なぜなのか?以下の行を追加して、実行してみると、

print "utf8 tag = ", utf8::is_utf8($tag), "\n";
print "utf8 title = ", utf8::is_utf8($title), "\n";

utf8 tagは1であり、utf8 titleはundefだ。つまり、もともとutf8のフラグが付いているスカラー$vから変換した$tagにも中身はアルファベットであるにもかかわらずutf8フラグが付いているのだ。一方、$titleはencodeしたので、utf8フラグはついていない。では、この二つを結合した$cmdはどうなるかというと、utf8フラグが付いてしまうのだ。この問題の凶悪なところは$cmdの内容をperlスクリプト内でprintを用いて出力しても、見た目は正しく見えるのでだが、systemに渡すと、その時点で文字化けが発生してしまうことだ。文字化けを避けるためには、$tagを結合する前に、やはりencodeしてutf8フラグを消さなければならないということだ。好きなコードでよいだろうが、アルファベットであることが確実ならば、asciiにでもしておけばよい。