LinuxカーネルHack: Btrfsのコードに散りばめられたBUG_ONマクロが示す兆候

BUG_ONマクロは、Linuxカーネルにおいて、引数に与えられた式を評価した結果が真となることが想定されない箇所(つまり、真ならバグ)で使われる。簡単に言うと、Cのassertのようなもの。ただし、assertよりもきつくて、BUG_ONの引数が真になると、カーネルパニックを引き起こす。つまり、BUG_ONの引数が真になったらカーネルが即死なので、利用には十分に気をつけるべきマクロである。

Btrfsのコードを読み始めて薄々感じていたけれども、BtrfsではこのBUG_ONマクロが過剰なまでに多く使われている。

他のファイルシステムと比べて、どの程度過剰か数字を取ってみた。(数字は、最新のメインラインより計測。tail -n +2は、findの結果の1行目にfsが含まれることで、xargs grepで2重に計測してしまうのを防ぐトリック。BUILD_BUG_ONは、無害とみなした。)

枯れているext2では7回、ext3では2回。ext4ではぐっと増えて158回。Btrfsは断トツの797回。この結果から、BtrfsがいかにBUG_ONを使っているかわかる。

% find fs -type d | tail -n +2 | xargs grep -r "BUG_ON" | grep -v "BUILD_BUG_ON" | perl -ne 'print "$1\n" if /fs\/(.+?)\//' | sort | uniq -c | sort -nr
    797 btrfs
    745 ocfs2
    265 ntfs
    158 ext4
    137 logfs
     96 reiserfs
     75 ceph
     66 gfs2
     46 nfs
     41 jffs2
     32 nfsd
     28 partitions
     26 notify
     24 nilfs2
     24 ecryptfs
     19 fuse
     13 xfs
     13 configfs
     12 udf
     12 sysfs
     12 fscache
     12 9p
     11 coda
     10 afs
      9 jfs
      9 fat
      8 ufs
      8 lockd
      7 ext2
      7 exofs
      7 affs
      5 jbd2
      5 dlm
      4 quota
      4 hfs
      4 devpts
      4 cifs
      3 hugetlbfs
      3 hfsplus
      3 freevxfs
      3 cachefiles
      2 sysv
      2 openpromfs
      2 ext3
      1 ubifs
      1 romfs
      1 proc
      1 isofs
      1 autofs4

具体的にはどのようなコードか?

BUG_ONとその数行前を見ると、戻り値のチェックにBUG_ONを使っていることがわかる。常にこういった使われ方をしているわけではないけれども、こういったBUG_ONの使われ方がBtrfsでは散見される。

% grep -rn -B3 "BUG_ON" fs/btrfs
fs/btrfs/inode.c-144-   inode_add_bytes(inode, size);
fs/btrfs/inode.c-145-   ret = btrfs_insert_empty_item(trans, root, path, &key,
fs/btrfs/inode.c-146-                                 datasize);
fs/btrfs/inode.c:147:   BUG_ON(ret);
--
fs/btrfs/inode.c-244-
fs/btrfs/inode.c-245-   ret = btrfs_drop_extents(trans, inode, start, aligned_end,
fs/btrfs/inode.c-246-                            &hint_byte, 1);
fs/btrfs/inode.c:247:   BUG_ON(ret);
--
fs/btrfs/inode.c-251-   ret = insert_inline_extent(trans, root, inode, start,
fs/btrfs/inode.c-252-                              inline_len, compressed_size,
fs/btrfs/inode.c-253-                              compressed_pages);
fs/btrfs/inode.c:254:   BUG_ON(ret);
[...]

約800ヶ所あるBUG_ONのうち、約半分がこのような関数の戻り値のチェックに使われている。

% grep -r "BUG_ON" fs/btrfs | grep ret | wc -l
391

これは何を示唆するのか?Btrfsの開発に何が起こっているのか?

達人プログラマー(ピアソンエデュケーション)に、「割れた窓を放置しておかないこと」という教訓が書かれている。

我々も過去に、窓が割れただけで清潔、機能的なシステムがあっという間に崩壊していくさまを目の当たりにしたことがあります。これ以外の要因でソフトウェアが腐っていく場合も確かにあり、そのうちのいくつかは他のセクションでも説明していますが、放置しておくことは他のどのような要因よりも腐敗を「加速」させるのです。

想像するに、Btrfsの開発では、過剰なまでにBUG_ONを使う悪い習慣が根付いてしまっているのだと思う。習慣に従って、無意識にBUG_ONを使ってしまうと、考えるべきエラー処理を考えなくなってしまう。エラー処理は、ソフトウェアの品質に左右する重要な視点なので、それを考えることが無くなってしまうと、おのずとソフトウェアの品質を低下させてしまう。この習慣を止めなければ、いつまでたっても、コードの品質が上がらないばかりか、割れ窓理論に従って、今後もBUG_ONが増殖していく。

もし、Btrfsがプロダクション環境で使える品質か、と聞かれたら、僕はNoと言う。もちろん、BUG_ONの観点だけで、その結論を出すのはどうかとは思うけれども、少なくとも、これほどまでBUG_ONが使われているという事実だけでも、プロダクション環境での利用は危険すぎると判断せざるを得ない。

Btrfsの開発者が、BUG_ONに対する意識を変えて、現状あるBUG_ONを適切なエラー処理に置き換えていくことが、小さいことかもしれないけれども、Btrfsをプロダクション環境での利用に近づける一歩になるのだと思う。

BUG_ON撲滅運動できないかなー。