NAME
Test::LeakTrace::JA - メモリリークを追跡する
VERSION
This document describes Test::LeakTrace version 0.11.
SYNOPSIS
use Test::LeakTrace;
# simple report
leaktrace{
# ...
};
# verbose output
leaktrace{
# ...
} -verbose;
# with callback
leaktrace{
# ...
} sub {
my($ref, $file, $line) = @_;
warn "leaked $ref from $file line\n";
};
my @refs = leaked_refs{
# ...
};
my @info = leaked_info{
# ...
};
my $count = leaked_count{
# ...
};
# standard test interface
use Test::LeakTrace;
no_leaks_ok{
# ...
} "description";
leaks_cmp_ok{
# ...
} '<', 10;
DESCRIPTION
PerlのGCはリファレンスカウンタを用いたものなので,オブジェクトが開放されるタイミングが明確であることや体感速度が高速であることなど数々の利点があります。 その一方で,循環参照を開放できないこと,Cレベルでの操作でミスしやすいなど,問題点がいくつかあります。それらの問題点のほとんどはメモリリークに関することですから,メモリリークを追跡することは非常に重要な課題です。
Test::LeakTrce
はメモリリークを追跡するためのいくつかのユーティリティとTest::Builder
ベースのテスト関数を提供します。このモジュールはPerlのメモリアロケーションシステムであるアリーナを走査するため,SVに関することであれば与えられたコードのどんなメモリリークでも検出できます。つまり,Perlレベルでの循環参照を始めとして,XSモジュールやPerl自身のバグによるメモリリークを追跡することができます。
ここでリークとは,特定のスコープ内で新たに作成されて,そのスコープ終了後にも残っている値を意味します。これは,新たに作成されたグローバルな値やPerlが暗黙のうちに作成するキャッシュの値も含みます。たとえば,リーク追跡を行っている最中に新たに名前つきサブルーチンを定義すれば,それはリークとみなされます。また,継承したメソッドを呼び出したり,オブジェクトを作成したりするだけで様々なキャッシュが生成され,リークが報告される可能性があります。
INTERFACE
Exported functions
leaked_info { BLOCK }
BLOCKを実行し,追跡結果をリストで返します。 結果はリークした値のリファレンス,ファイル名,行番号の三要素を持つ配列,つまり[$ref, $file, $line]
のリストとなっています。
なお,この関数はPerl内部で使用する値を返す可能性があります。そのような内部用の値を変更するとPerl実行環境に致命的な影響を与える可能性があるので注意してください。また,配列やハッシュの要素として,リファレンスではない配列やハッシュそれ自体が含まれる可能性があります。そのような値は通常Perlレベルで操作することができません。たとえばData::Dumper
などで出力することはできません。
leaked_refs { BLOCK }
BLOCKを実行し,リークしたSVのリファレンスのリストを返します。
map{ $_->[0] } leaked_info{ BLOCK }
と同じですが,より高速です。
leaked_count { BLOCK }
BLOCKを実行し,リークしたSVのリファレンスの個数を返します。
leaked_info()
とleaked_refs()
もスカラコンテキストでは個数を返しますが, leaked_count()
はコンテキストに依存しません。
leaktrace { BLOCK } ?($mode | \&callback)
BLOCKを実行し,その中で起きたメモリリークを*STDERR
に報告します。
メモリリークの報告は$modeで指定したモードに従います。 受け付ける$modeは以下の通りです:
- -simple
-
デフォルトのモードです。リークしたSVの型とアドレス,ファイル名,行番号を報告します。
- -sv_dump
-
-simpleに加えて,
sv_dump()
でSVの中身をダンプします。 これは,Devel::Peek::Dump()
の出力とほぼ同じです。 - -lines
-
-simpleに加えて,リークしていると見られる行の周辺を出力します。
- -verbose
-
-simpleと-sv_dumpと-linesの全てを出力します。
より細かな制御のためにコールバックを指定することもできます。 \&callbackはリークしたSV毎に呼び出され,その引数はリークしたSVのリファレンス,ファイル名,行番号の3つです。
no_leaks_ok { BLOCK } ?$description
BLOCKにメモリリークがないことテストします。 これはTest::Builder
ベースのテスト関数です。
なお,BLOCKは複数回実行されます。これは,初回の実行でキャッシュを用意する可能性を考慮するためです。
leaks_cmp_ok { BLOCK } $cmp_op, $count, ?$description
BLOCKのメモリリーク数と特定の数値を比較するテストを行います。 これはTest::Builder
ベースのテスト関数です。
なお,BLOCKは複数回実行されます。これは,初回の実行でキャッシュを用意する可能性を考慮するためです。
Script interface
Devel::LeakTrace
と同様に,スクリプトのリーク追跡のためにTest::LeakTrace::Script
が提供されます。use Test::LeakTrace::Script
宣言の引数はleaktrace()
と同じです。
$ TEST_LEAKTRACE=-sv_dump perl -MTest::LeakTrace::Script script.pl
$ perl -MTest::LeakTrace::Script=-verbose script.pl
#!perl
# ...
use Test::LeakTrace::Script sub{
my($ref, $file, $line) = @_;
# ...
};
# ...
EXAMPLES
Testing modules
以下はモジュールのメモリリークをチェックするテストスクリプトのテンプレートです。
#!perl -w
use strict;
use constant HAS_LEAKTRACE => eval{ require Test::LeakTrace };
use Test::More HAS_LEAKTRACE ? (tests => 1) : (skip_all => 'require Test::LeakTrace');
use Test::LeakTrace;
use Some::Module;
leaks_cmp_ok{
my $o = Some::Module->new();
$o->something();
$o->something_else();
} '<', 1;
GUTS
Test::LeakTrace
はアリーナを走査します。アリーナとは,Perlが作成するSVのためのメモリアロケーションシステムであり,sv.cで実装されています。 アリーナの走査にはsv.cにあるS_visit()
のコードを元にしたマクロを用いています。
さて,アリーナを走査すれば,メモリリークの検出そのものは簡単にできるように思えます。まず,コードブロックを実行する前に一度アリーナを走査し,全てのSVに「使用済み」の印を付けておきます。次に,コードブロック実行後にもう一度アリーナを走査し,使用済みの印がついていないSVがあれば,それはコードブロック内で作成され,開放されなかったSVだと考えます。あとはそれを報告するだけです。実際には,SVに対して使用済みの印を付けるスペースがないため,インサイドアウト法を応用して外部のコンテナに使用済みの印を保存します。 これを仮にPerlコードで書くと以下のようになります。
my %used_sv;
foreach my $sv(@ARENA){
$used_sv{$sv}++;
}
$block->();
my @leaked
foreach my $sv(@ARENA){
if(not exists $used_sv{$sv}){
push @leaked, $sv;
}
}
say 'leaked count: ', scalar @leaked;
リークしたSVを得るだけならこの方法で十分です。実際,leaked_refs()
とleaked_count()
はこのような方法でリークしたSVやその個数を調べています。
しかし,リークしたSVのステートメントの情報,つまりファイル名や行番号を得るためにはこれだけでは不十分です。Perl 5.10以降にはSVが作成されたときのステートメント情報を追跡する機能があるのですが,この機能を利用するためには,コンパイラオプションとしてに-DDEBUG_LEAKING_SCALARS
を与えてPerlをビルドしなければなりません。
そこで,Test::LeakTrace
では拡張可能なPL_runops
を利用して,Perl VMがOPコードを実行する1ステートメント毎にアリーナを走査し,ステートメント情報を記録します。これは,1ステートメント毎にマーク&スイープのような処理を行うのに等しく,非常に時間が掛かります。しかし,Perlを特殊な条件の下でビルドする必要もなく,バージョンに依存した機能もほとんど使用しないため,多くの環境で動かすことができます。
また,no_leaks_ok()
のようなテスト関数はまずleaked_count()
でリークしたSVの個数を得てから,必要に応じてリークした位置を特定するためにleaktrace()
を実行するため,テストが成功する限りは時間の掛かる追跡処理はしません。
DEPENDENCIES
Perl 5.8.1 or later, and a C compiler.
CAVEATS
Test::LeakTrace
はDevel::Cover
と一緒に動かすことはできません。 したがって,Devel::Cover
の元で動いていることが検出されると,テスト関数は何も行わずにテストをパスさせます。
BUGS
No bugs have been reported.
Please report any bugs or feature requests to the author.
SEE ALSO
For guts:
sv.c.
AUTHOR
Goro Fuji <gfuji(at)cpan.org>.
LICENSE AND COPYRIGHT
Copyright (c) 2009, Goro Fuji. Some rights reserved.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.