【ごめんなさい】連載の実装漏れとバグ

Wed Oct 18 10:13:00 JST 2023 (modified: Wed Oct 18 10:47:34 JST 2023)
views: 1729, keywords:自作シェル, bash, 連載 この記事は最終更新日が1年以上前のものです。

 連載「魅惑の自作シェルの世界」では、自作シェルのコードを書く手順を説明しながらシェルの仕組みを説明していますが、このスタイルだとどうしてもバグがまざってしまいます。で、それは覚悟でやっており、読者さんから報告があるとうれしいなとまで考えておりますが、それを超えるやらかしをやってしまいましたので報告いたします。あと、もうひとつバグを見つけたのでそれも報告しますので、報告事項は2点です。

パイプライン処理の実装漏れ

 で、やらかしですが、2023年8月号の最後で、パイプラインが動作するか検証していますが、実はこのパイプラインは動いても、データが大きいとうまく動きません。コマンドを実行している部分を見ると、次のように、子のプロセスで実行しているコマンドを、親のプロセスが待つというコードになっています。

fn exec(&mut self, core: &mut ShellCore, pipe: &mut PipeRecipe) {
       if core.run_builtin(&mut self.args) {
           return;
       }
   
       self.set_cargs();
       match unsafe{unistd::fork()} { //子のプロセス
           Ok(ForkResult::Child) => {
          (コマンドを実行)
           },
           Ok(ForkResult::Parent { child } ) => { //親のプロセス
               pipe.parent_close();
               core.wait_process(child); //ここでコマンドの終了を待っているけどこれでは駄目
           },
           Err(err) => panic!("Failed to fork. {}", err),
       }
   }

この書き方だと、今実行しているコマンドが終わらないとパイプラインにある次のコマンドが立ち上がりません。したがって、今実行しているコマンドが、パイプにバッファを超える量のデータを通そうとすると、次のコマンドがデータを読むのを待ってしまって、デッドロックが起こります。

 で、どうするかというところですが、いま連載中で実装しているリダイレクトが一旦おわったところで、改めて実装を説明します。待てない人には、11月号のコードに対応策を施したブランチを作ったので、それで動作確認してみてください。seq 10000000000 | revを実行すると、対策前のパイプラインは止まって、対策後のパイプラインは問題なく実行されます。

ミスの総括

 次の2点ですね・・・

  • ちょうどページのキリがよいので満足してしまった点
  • テストを少ないデータ量でしかやってなかった点

本業もあるのでどうしても短時間で一気に執筆を済ませようという意識が頭をよぎってしまうのですが、あまり慌てずにテストをすべきでした。

もう1点

 これももうちょい考えるべきでしたが、たとえばls | | revなどと打つと、シェルがおかしくなって以後コマンドを受け付けなくなり、killall sushしないとexitできなくなります。

原因

 現状(ブランチsd/202311_4)のコードでは、パイプラインをパースしている部分で、パイプのあとにコマンドがないと、無条件でユーザーにコマンドを入力するように促します。次のように、63行目でコマンドの読み込みに失敗すると、66行目で入力待ちになります。

pub fn parse(feeder: &mut Feeder, core: &mut ShellCore) -> Option<Pipeline> {
   (略)
       while Self::eat_pipe(feeder, &mut ans, core){
           loop { 
               Self::eat_blank_and_comment(feeder, &mut ans, core);
               if Self::eat_command(feeder, &mut ans, core){
                   break;
               } //↓コマンドが読み込めないと、無条件でユーザーに入力を促すようになっている
               if ! feeder.feed_additional_line(core) {
                   return None;
               }
           }
       }
   
       Some(ans)
   }

なので、ls | |などと入力すると、パイプの間にコマンドがないし、ユーザーがコマンドを入力しても、それは2つめのパイプの後ろに入って意味がないので、おかしなことになります。

 対策は、

  • feederのなかに文字がなければ入力待ちの状態にする
  • そうでなければエラーを出す

というものになります。これについては12月号の冒頭でFixしようと思います。

ミスの総括

 このミスについては自分の想像力が及ばんかった、いじわるなケースのテストが抜けていたというのが理由になります。ただ、 そのようなミスを防ぐ方法論も現場レベルではあるはずですし、数学か論理学でしっかり考える方法もあるはずなので、ちょっと筆者が不勉強かもしれません。ただ、連載については、気軽になにか手を動かして作りたいという人にネタを提供しているという気分で書いておりますので、ご容赦ください。

最後に

↓8月号の最後。

・・・「このへんで。」じゃあないんだよ・・・。

以上です。

ノート   このエントリーをはてなブックマークに追加 
 

prev:リダイレクトのエラーの謎4(たぶん解決) next:日記(2023年10月29日)

やり散らかし一覧

記事いろいろ