• 作成:

UNIX(プロセス管理), アルゴリズムとデータ構造2(再帰), yesodアプリケーションのデッドロックの根本的原因がわからない

UNIX

プロセス管理 - UnixClassWiki

物理的リソースとしてのCPU数 < 仮想的な計算リソースとしてのCPU数

と書かれてて, 普通の場合はまあその通りだろうと思ったのですが, 最近はハイパースレッドとかあるので, 物理的なCPU数という概念が曖昧になってきていますよね. なので, 捉え方によっては逆になることもあるのではないかとか思ってしまいました. 例えばNUMA構成のCPUをNUMA無効にしたら仮想CPU数をが半減したりするので逆転してしまう可能性があるのではないかとか.

C言語の生のforkの例初めてみたかも知れない. こういう動きをするんだな… pthreadとかなら少し使ったことありますが, 実はC言語でforkは使ったことがない. シェルスクリプトでならありますけど.

調べてみよう

コマンドuptimeのマニュアルを調べてみよう。またロードアベレージの意味も調べてみよう。

現在時刻、システム起動からの時間、システム上にいるユーザ数、および直近1、5、15分間の実行キューに存在するジョブの平均数を表示します。

調べてみよう

プロセスの状態を表しているSTATの欄をみるとR/S/D/Z/Tの後ろにさらに文字がついていますがこの意味は何でしょうか?

私の環境ではSTATにそれ以外の情報は表示されないなあと思って調べてみたら, Wはスワップアウト, Nは低優先度を意味するみたいですね. しかし, manには載っていない…

sudo kill –1 1が特別扱いされて実行されないという話を聞いて, 確かに実行されないしなあと思って coreutils/kill.c at master · coreutils/coreutils を見てみたのですが, 特にcoreutilsでは1が特別扱いされてないないと判断しました. カーネルのソースを読もうと思ってsudo make TAGSでTAGSを生成したらタグファイルが183Mになってしまいました. ctagsの関数の参照がうまく動かなくて読むのが大変でした. gtagsなら参照出来るので, universal ctagsに統一するという考えは諦めるべきでしょうか.

カーネルのソースコードを読んでも何処で処理されているのかわかりませんでした. Can root kill init process? - Unix & Linux Stack Exchange を読んでみましたが, 何処で処理されているのかはわからずじまい…

「LinuxカーネルのCPUスケジューラは基本的にCFSしかない」という話を聞いて, そう言えば, BFQはマージされたようにIOスケジューラは複数あるのにCPUスケジューラはBFSがマージされないのは何故だろう? と思いました. 単にBFSの性能があまり良くないだけなのでしょうか. カーネルのmenuconfigでスケジューラの設定が無いか調べてみようと思いましたが, Completely Fair Scheduler について - カメのたわごとによるとスケジューリングクラスと言ってプロセスごとに実行時に別のスケジューリングを持つことが出来るようですね. ubuntu - Linux default scheduler alternatives - Stack Overflow を読む限り, ハードコーディングされてるからそもそも設定がないので, カーネルオプションでデフォルトのスケジューリングを変更することは出来ないようですね. linuxカーネルのソースコードを書き換えてPID1のスケジューリングを変更するしかないようです.

アルゴリズムとデータ構造2

「試験の時にスマートフォンに電卓を入れて置いて欲しい, PCは不可」 という話があったので, 電卓にmaximaを使っている身としては, つい 「スマートフォンにdebian入れたらダメですか」 という質問をしてしまいました.

再帰をループを使った形に書き直す例としてフィボナッチ数.

ハノイの塔を解くような複雑な再帰はループに書き直すのは大変.

int fact(int n, int a = n) {
    if (n == 0 || n == 1)
        return a;
    else
        return fact(n-1, a*(n-1));
}

というコードが出てきたのですが, C++でこんな引数のデフォルト値指定を許可する処理系拡張は存在するのでしょうか? 標準ではないことは確実だと思うんですが. 不明な拡張のため, これがどういう意図なのかわからず割と混乱しました.

yesodアプリケーションのデッドロックの根本的原因がわからない

PGPOOLSIZEの上限に行ってしまい, 互いにrunDBの終了を待ってデッドロックしていることはわかってきました.

なので, lift defaultLayoutしているところを書き直して, runDB区間が次のrunDBが始まる前に, きちんと終了するように書き換えればデッドロックしなくなると思いました.

しかし, 実際はコネクションを使い果たしてしまいます, 推理していた原因が違ったので, 根本的原因がわからなくなってしまいました.

具体的にどのハンドラで問題が起きているのかがよくわからない. スレッドに非同期例外を投げてスタックトレースを出力するという手法は, ThreadIdは整数がわかっても整数から構築することが出来ないのでghci上でThreadにthrowToすることが出来ないという問題で実行不可能でした. どなたかhaskellのthreadのstack traceを取る方法を知っている方は居ないでしょうか.

GHC.StackcurrentCallStackでコールスタックを取れるので全てのスレッドでこれを出力してみようと思いましたが, 空リストが帰ってきました. どうやらプロファイルを有効にしてビルドしないとダメだったようですね.

現状PGPOOLSIZEをデフォルトの10から100にするという方法でリソース取得でデッドロックしないようになっていますが, この先アクセスが増えた場合にデッドロックが起きそうで非常に気味が悪いです.

デッドロックが発生している時にpsqlでselect * from pg_stat_activity;でコネクションの実行しているqueryを見てみてもどれもBEGINの状態でidleしてるんですよね. 多分runDBを開始した関数内でrunDBがまた起きてclose出来ずにデッドロックしているということなのでしょう.

FoundationrunDBを以下のようにしてみると,

    runDB action = do
        threadId <- liftIO myThreadId
        $logDebug $ "runDB start: " <> tshow threadId
        master <- getYesod
        r <- runSqlPool action $ appConnPool master
        $logDebug $ "runDB end: " <> tshow threadId
        return r

デッドロックする時は, endを出力せずにstartだけを吐き出すようなログになります. コネクションを取り合っているという予測はおそらく正しいと思います.

HTTP通信を同時に4以上行わないとか設定できれば, PGPOOLSIZEを増やすだけでほぼデッドロックしないと言えると思うのですが, ghc-workerによる制限だけで十分に制限されているのでしょうか.

現在はデッドロックしていないため, もうアクセス集中で実際にデッドロックが発生してから考えた方が良いのかもしれません… 要するに調査を諦めるということですが.