読者です 読者をやめる 読者になる 読者になる

暴走したPerlをとめるまで

追記2

以下本文の内容に関して、 id:hkoba501 さんが考察記事を書いてくださいました。
hkoba.hatenablog.com

なんとありがたい……大変詳しく、何よりすごく勉強になります。
この後あらためて、じっくり拝読します。 id:hkoba さん、ありがとうございました!

追記1

その後、Perl入学式の先輩方といろいろ話していたら、これは他の環境では再現されないので、エディタのシンタックスチェッカーに原因があるのでは? 等の指摘があり、たしかに別のLinux環境で試してみるとまったく暴走したりはしないので、以下の解消法(?)というか考え方はアサッテかもしれません。

現象

以下のようなコードを書いていて、

#!/usr/bin/env perl
#
# main.pl

use strict;
use warnings;
use feature 'say';
use Sample;

print 'input >>> ';
chomp(my $foo = <STDIN>);

say Sample::init($foo);
# Sample.pm

package Sample {
    use strict;
    use warnings;

    sub init {
        my $bar = shift;
        if ($bar eq 'end') {
            return $bar;
        }
        else {
            print "$bar >>> ";
            chomp($bar = <STDIN>);
            init($bar);
        }
    }
}

1;

こんな感じに動作させたかったとします。

DEMO

f:id:note103:20160503152810g:plain

実際、そんな風に動くのですが、この時、後者の Sample.pm のサブルーチン init の前に、うっかり以下を入れてしまうと、

    init();

けっこう面倒なことになります。というか、なりました。

具体的にはこんなコードですが、真似しなくて大丈夫です。

# Sample.pm
#
# 失敗ヴァージョン

package Sample {
    use strict;
    use warnings;

    init(); #<= ミス

    sub init {
        my $bar = shift;
        if ($bar eq 'end') {
            return $bar;
        }
        else {
            print "$bar >>> ";
            chomp($bar = <STDIN>);
            init($bar);
        }
    }
}

1;

この状態で呼び出し側の main.pl を開くと、

  1. まずエディタ(MacVim)が固まり、
  2. 何も反応しないのでエディタを強制終了し、
  3. そのまましばらくするとマシンの容量が加速度的に減っていき、
  4. ファンもワンワン鳴って怖いのでマシンごと再起動し、
  5. 「いまの何だったんだろ……」と、検証しようと思ってまた main.pl を開いてしまい、
  6. エディタ(MacVim)が固まり、
  7. 何も反応しないのでエディタを強制終了し、(以下繰り返し)

みたいなことをしばらくやっていました。(ギャグみたいだけど本当に)

原因

原因としては、最初から書いているとおり、

    init();

が不要というか、それが無限ループ的な何かを引き起こしているのだろうと想像しますが、それに気づくまではこのケースがいくら検索しても出てこなくて、たいそう時間を使ってしまったので一応書いておきました。

ちなみに、そもそもなんでそんな1行が入ってしまったの?という疑問に対しては、「いやー、最初は1本の.plファイルに書いていたんだけど、サブルーチンの部分だけモジュールに切り分けようと思ったら、最初のファイルで書いていたサブルーチン呼び出しの行を削除し忘れてしまって」と、答えたいと思います。

とはいえしかし、そんな程度のミスなんて日常茶飯事すぎて、今までもいくらでもやってきた&今後もやっていくであろうぐらいちょっとした間違いなのに、これだけ深刻というか、面倒な暴走状態に陥ったことはほとんどなかったので、本当にミステリーでしたね……

で、なぜ普段はほとんどこんな目に遭わずに済んだのか、そして今回はそれに遭遇してしまったのか、と考えると、実際の問題は上記の「サブルーチンを呼び出す行の消し忘れ」だけでなく、当のサブルーチン(Sample.pm内のinit)の書き方自体もおかしくて、具体的には最後のelse文の中で同じサブルーチンを呼び出しているのが悪手なのだろうな……とは思っています。

その再帰的なサブルーチン呼び出しと、上述の消し忘れた呼び出し行とが悪魔合体して、あまり自分的には例のない現象に至ったのかな……と。

で、そのサブルーチン内で同じサブルーチンを呼び出すという方法は、元はといえば while文でミスって無限ループするのが嫌でなんとなく使っていた方法でしたが、こうなると問題は while文を使うことではなく、そもそも無限ループが生じてしまう仕組みを理解していない点にあると気づかざるを得ませんね。まあ、今後の課題ということで……

プロセスの切り方

ところで、今回はこの現象の原因究明や解消方法についても結構考えましたが、副次的な問題として、こういう暴走状態になったときにどう対処すればいいか、ということもちょっと調べました。

というか、上記のようにぼくはwhile文で無限ループしたり、それのちょっとタチ悪い版で今回のようにマシンを再起動せざるを得ない状況に陥りがちなので、そのたびにマシン再起動するのもつらすぎる……ということで。

で、一番参考になったのは以下で、

topコマンドからkillする - ケーズメモ

$ top

して、対象と思わしきPerlのプロセスID(PID)を見つけたら、

$ kill -9 そのプロセスID

としたら切れました。自分にとっては革命的便利さ。

さらにちなみに、killコマンドの復習はこちらで行いました。ITproにかぎらず、こういうまとめ記事はいつもありがたいです。ジャンプの途中で広告が入っても構いません。

Linuxコマンド集 - 【 kill 】 プロセスおよびジョブを強制終了する:ITpro

以上です。