メモリリークとタイマ・カウンタ問題は時限爆弾 バグ

2019年4月23日バグの巣

時限爆弾 バグって何でしょう?

時限爆弾 バグ、何か危なそうな名前だと思いませんか? どんなバグなのかなとGoogle で 時限爆弾 バグを検索しても・・・何も出てきません。実は、時限爆弾バグというのはグータラ親父がある種類のバグを勝手にそう呼んでいるだけなので、世間一般では通じないバグの名前です。

それでは、なぜグータラ親父はわざわざこんな怪しげなバグの名前を使っているのでしょうか? 実はある種類のバグは、市場でそのバグが元で不具合が発生した時の影響範囲がとても大きくなるので、要注意だからです。どんな種類のバグを時限爆弾バグと呼んでいるかというと、① 動的資源のリークを原因にするバグ と ②タイマやカウンタのロールオーバー処理に起因するバグ の2種類です。

なぜこの2つの種類のバグを時限爆弾バグと呼ぶかというと、2つの特徴を持っているからです。その特徴というは、(a) 装置の電源を入れて暫くの間は何も問題無く動くのに、(b)一定の時間を経過すると全ての装置で問題が発生する、という物です。

(a) の装置の電源を入れてから暫くの間は問題無く動くとう特徴のため、社内のテストの期間には問題が起きずにテストで見つけられず、潜在バグとして市場に漏れだす事が多いです。暫くの間というのが数か月から数年に及ぶ場合があるので、時間の限られた社内テストを通過してしまうのですね。そして市場に漏れだしてしまうと、暫くの間問題無く動いた後に殆ど全ての装置で問題が発生してしまいます。新発売から間もない時期だと市場で実際に動いている装置の数は少ない事が多いのですが、数か月以上経つとさすがに多くの装置が市場で動いています。そしてこれらの殆ど全ての装置で不具合が発生してしまうので、、、メーカとしてはとっても恐ろしい状況が起こります。

その状況が、全ての製品にコッソリと取り付けられた時限爆弾のようなので、グータラ親父はこれらのバグを時限爆弾バグとして、特に注意していました。

動的資源のリークによる時限爆弾

それでは時限爆弾バグについて具体的に見ていきましょう、まず一つ目が動的資源のリークを起こすバグです。動的資源とは、必要になった時に獲得して使い終わったら返却するという使い方をする資源ですね。一番よく知られているのはOSが提供する動的メモリです。ネットでメモリリークを検索すると、いろいろな具体例が出てきますが、メモリリークについて簡単におさらいしておきましょう。

OSは動的メモリの提供機能を持っています。動的メモリとは、アプリケーションソフトが必要な時にOSからメモリを借用して使用し、使用が終わってそのメモリが必要が無くなったらOSに返却します。メモリというOSの管理している資源が、OSとアプリケーションとの間で行き来するので、動的(Dynamic)という名前が付いています。

この一連の処理の中で、アプリケーションソフトにバグがあって必要が無くなったのにそのメモリをOSに返す処理が抜けてしまう事で発生するのが、メモリリークです。OSが持っているメモリ資源の残量がソフトウエアの動作に従って徐々に減っていくので、まるで水のタンクから水が漏れて減っていっているように見えるので、リーク(漏れる)という言い回しが使われています。漏れたものは返って来ないので、メモリリークが続いてOSの持っているメモリ資源の残量がゼロになると、OSはアプリケーションソフトにメモリをかせなくなります、するとアプリケーションソフトはメモリが無いので処理ができなくなり、機能不良に陥ってしまいます。

このメモリリークは、アプリケーションソフトがメモリをOSに返す処理を抜かしてしまうのが原因なのですが、一度に借り受けるメモリの量やメモリを借り受ける頻度によって、メモリがリークする速度が変わります。リークする速度は変わるのですが、一旦リークして失われたメモリは回復される事は無いので、OSの持っているメモリ資源の残量は減り続けていき、復活する事はありません。そのために、動作している全ての装置で、いつかはメモリリークによる機能不良が発生してしまいます。そしの、このいつかというのが、リークの量によっては半年後だったり2年後だったりすのでで、とても厄介です。

このメモリリークが通常の処理で発生するなら、まだ社内のテストで見つけられる可能性もあります。OSの持っているメモリ資源のサイズを極端に小さくした検証専用の特殊なソフトウエアを作って、色々な操作や機能を加速するような事を行えば、メモリの残量がゼロになるような状態を短期間に発生さえる事ができる場合もあります。 しかし、メモリリークが何かの条件で引き起こされる異常処理の中で発生していると、正常処理をいくら加速してもリークは加速されないので短期間でメモリの残量がゼロになるような状態が起きず、やはり社内のテストでは検出が難しくなります。 そして、残念な事にソフトウエアのバグは異常処理の中に好んで住み着いているので、なかなか厄介です。

動的資源は動的メモリだけでしょうか?

メモリリーク時限爆弾バグとの紹介をしてきたのですが、動的資源というのはメモリ以外にもあるのでしょうか? 実はリークする可能性のある動的資源は他にも2つあります。一つはOSの提供するメモリ以外の動的資源で、もう一つはアプリケーションソフトウエアが提供する動的資源です。

OSの提供する動的資源としては、メモリ以外にも通信用のソケットなどがあります。こちらも、OSの初期設定で最大の個数が決まっていて、アプリケーションソフトウエアは必要な時にOSからソケットを借用し、使い終わったら返すという処理方法を採用する場合もあります。 もちろん、ソケット資源の量に余裕があれば動的な運用をせずに、起動時に必要な数のソケットを獲得してしまい、それをずっとそのまま使い続けてOSに返さないというソフトウエアの設計方法もあります。この場合はソケットについては動的資源として使うっているのではなく、静的資源として使っている事になるのでリークは起きないですね。ただし、組み込み系のソフトウエアではいろいろなハードウエア上の制約があって、このように静的資源として使う事ができない場合も良くあります。その様な時には、ソケットも動的資源になるのでリークする可能性があり、時限爆弾バグの原因となるかもしれません。

そして、残る1つのアプリケーションソフトウエアが提供する動的資源が、わりと見落とされ易い厄介なバグです。動的資源とは、使う時に借りて使い終わったら返すという処理のある資源です。この使い終わったら返すを忘れるとリークが起きます。という事は、借りて返すという処理があればそれはOSが管理する資源でなくてアプリケーションが管理している資源でも動的資源で、そこには資源リークの危険性が潜んでいるという事になります。

少し具体的な例を紹介しましょう。アプリケーションAが何等かの管理テーブルを共有領域に持っていて、その管理テーブルのエントリ数は最大で10件だとします。その管理テーブルに登録されたアプリケーションソフトウエアだけが、機能BをアプリケーションAから提供されるような仕組みになっていると想定しましょう。アプリケーションCは機能Bを使いたいときには、管理テーブルからエントリを1つ獲得してそこに自分はアプリケーションCで機能Bを使いたいと書き込みます。その上でアプリケーションAに機能Bを要求します。そして、機能Bが終了したらアプリケーションCは管理テーブルから自分の情報を削除して、エントリを1つ返却します。 

このような仕組みでは、共有領域にある管理テーブルのエントリが動的資源になっています。全部で10個のテーブルエントリがあって、それをいろいろなアプリケーションソフトで必要な時に使うのですが、どれかのアプリケーションソフトがテーブルエントリの返却を忘れると、テーブルエントリの残量はだんだんと減っていきます。そのうちにテーブルエントリの残量がゼロになると、それ以降はどのアプリケーションソフトも機能Bが使えないという状態になってしまします。 これのように、OS以外にも動的に使うテーブルエントリの様な動的資源をアプリケーションソフトウエアが持っていると、そこにも資源リークのバグが潜む可能性がでてきます。

OSの提供する動的資源は比較的意識し易いのですが、アプリケーションソフトウエアが独自に作っている動的資源は、うっかりすると見落としがちなのですが、これも時限爆弾バグの原因になり得ますので注意が必要です。

動的資源のリークを社内テストで見つけましょう

動的資源のリークは、社内テストにリークの確認を目的としてテストを入れる事で、ある程度は防ぐ事ができます。やり方はいろいろありますが、何等かの方法で動的資源の残量を知る仕組みをソフトウエアの中に組み込んでおけば、テストが可能になります。 テストの考え方は簡単で、まず1度動的資源の残量を確認しておき、その後様々な処理を実行します。 処理の実行後に再度資源の残量を確認して、処理の前と変わっていなければ、その間に動的資源のリークが発生していなかったと考える事ができます。

様々な処理のなかに、通常の正常な処理に加えて高負荷を掛けた状態での長時間の動作とか異常が繰り返し発生する状態での長時間の動作など、動的資源のリークが潜在していたらそれを目立たせるような処理の加速の工夫をしておく事も効果があります。異常状態の繰り返しなど、テストの環境や条件を揃える事が難しいものもあるので、全てのリークを検出する完全なテストにはなりませんがそれでもリークによるバグを見逃すリスクは大幅に減らす事はできます。

タイマやカウンタのロールオーバー問題も時限爆弾です

動的資源のリークと並んで時限爆弾バグとなるのが、タイマやカウンタのロールオーバー処理に関わるバグです。良く知られているロールオーバー処理に関わるバグは、49日問題とか497日問題ですね。装置の起動から49日や497日が過ぎた時に何かの不具合が発生するという問題で、ネットで検索するといろいろな説明が出てきます。どちらもOSのタイマが符号なしの32bit整数で作られている時に、そのタイマがロールオーバーするのが起動後49日(1m秒の精度のタイマの場合)や497日(10m秒の精度のタイマの場合)で、そのタイミングで何等かの問題が起きるので49日問題とか497日問題と呼ばれています。

ところで、タイマやカウンタのロールオーバー処理に関わるバグって何でしょうか? 少しおさらいをしておきましょう。一般的なタイマやカウンタは起動時に初期値として0が設定sれて、そこから1ずつ増えていきます。このタイマやカウンタが符号なしの32bit整数で作られていると、その最大値は 0xFFFFFFFF です、10進数だと4294967295ですね。そして、最大値の4294967295からさらに1増えると、値は4294967296 にはならずに 0 に戻ります。32bitの符号なし整数が、保有する最大数を超えて初期値のゼロに戻る事をロールオーバと呼んでいます。このあたりは 32bit符号なし整数のコンピュータの中での表現方法についてネットで調べると、なぜ0になるのか解説がありますので、気になる人はそちらを探して下さい。

さて、32bitの符号なし整数で作られているタイマやカウンタが、最大値の 4294967295 から 1 増えると 0 に戻るのは別に問題ではありません。問題を起こすのは、そのタイマやカウンタを使っているアプリケーションソフトの側です。タイマでもカウンタでも問題が起きる仕組みは同じなのでタイマを例にして考えてみましょう。タイマは、大抵は時間の経過を測るために使います。今現在のタイマの値ー少し前のタイマの値 という計算をすると、少し前から現在までに経過した時間が測れます。XX秒経過したどうかを判定する時に使いそうですね。 また特定の時刻が来た事を知るには、現在のタイマの値>=特定の時刻を示すタイマの値 という判定でできますね、MM時YY秒になったら何かを処理するという時に使います。

ところが、タイマがロールオーバーした場合は、タイマの値が時間とともに減ってしまいます。単純な話で、さっきは 4294967296 だったタイマの値が、ロールオーバーして 0 になった後に 103回カウントアップされたとすると今の値は 103 です。時間の経過ともにタイマの値が減ってしまっています。 この場合には、今現在のタイマの値ー少し前のタイマの値 とう計算で経過時間を測ろうとすると、変な事が起きますね。これがタイマーのロールオーバ処理に関わる問題です。

カウンタにはダウンカウンタもあるので忘れずに

ここで説明した内容は、タイマやカウンタの値が時間とともにその値が増えていく事を前提にしています。通常はタイマは時間とともに値が増えていくのが普通なのですが、カウンタには時間と共に値が増えていくアップカウンタと、時間と共に値が減っていくダウンカウンタの両方があります。 ダウンカウンタの場合には、値がゼロになった時をトリガとして何等かの処理を行うという使い方をする事が多いので、ロールオーバが発生した時の処理で問題が起こる事は少ないのですが、それでもプログラムコードの作り方によってはロールオーバ問題を抱え込んでしまう事もあり得ます。

もちろん、タイマーやカウンタを使うアプリケーションソフトが、タイマやカウンタがロールオーバした時の事を想定したプログラムコードを組んであれば問題は起きません。大抵の場合は問題は起きないはずなのですが、たまたま経験の浅いプログラマーがコーディングを担当したとか、バグ修正のために急いでコードを作成したのでうっかりしていた、というようなささいな原因で、タイマやカウンタのロールオーバ処理をコードに組み込み忘れる事は起こり得ます。

そして、このロールオーバー処理の組み込みを忘れていたとしても、ロールオーバが発生するまでの間は、そのソフトウエアは何も問題なく動いて社内のテストも通ります。タイマがロールオーバする時までは、何も問題は起きないので潜在しているバグを見つけるのはなかなか難しいです。そして、市場で実際に装置が使われて長い時間が経過して、タイマがロールオーバーを迎えると突然装置がおかしくなってしまいます。 これも、装置が動作しはじめてから長時間が経過すると全ての装置で問題を引き起こしてしまうので、資源のリークと同じく時限爆弾バグです。

ロールオーバー問題は初期値の工夫で事前に確認しましょう

タイマやカウンタのロールオーバ処理にかかわるバグは、そのカウンタやタイマがロールオーバしないと発覚しません。ですので、社内テストでは特別な工夫をしておかないと、なかなか見つからない事が多いです。でも、タイマやカウンタがロールオーバをすればバグがわりと簡単に問題を引き起こすので、バグを発見する事ができます。そこで、ちょっとした工夫でタイマやカウンタがロールオーバする状態を作ってあげると、社内テストでのタイマやカウンタのロールオーバ問題の発見が楽になります。

どんな工夫をするかというと、タイマやカウンタの初期値を 0 ではなく、最大値に近い値にしておくという物です。タイマやカウンタは、0 から計数を始めなければならないという制限は無い事が多いです。なので、初期値を最大値に近い値にしておいても別に問題にはならないです。 そして、初期値を最大値に近い値にしておけば、ソフトウエアを短い時間動作させただけでそのタイマやカウンタがロールオーバを迎えるので、もしロールオーバに関わる潜在バグがあれば、そこで発見できます。 

時限爆弾バグとして、動的資源のリークタイマやカウンタのロールオーバに関連するバグについて紹介してきました。どちらのバグも、装置が在る程度の長時間動作をしてから、全ての装置で問題を引き起こすので、一旦発生すると影響範囲がとても広くなります。とっても嫌な事なので、グータラ親父は時限爆弾バグという変わった名前を付けて、特に意識してバグが無い事の確認をしていました。 皆さんの開発しているソフトウエアにも、時限爆弾バグが潜んでいないと良いですね。

バグの巣に戻る