dorapon2000’s diary

忘備録的な。セキュリティとかネットワークすきです。

VSCodeで「ターミナル プロセスが起動に失敗しました」

昨日までは普通にVSCode上でシェルが使えてたのに、突然起動に失敗したといってシェルが開かなくなってしまったので、その解決方法を残します。

環境

  • maxOS Big Sur 11.5.2
  • VSCode 1.59.1
  • fish

症状

VSCode上でターミナルを開こうとすると、あるいはデバッグを実行しようとして「ターミナル プロセスが起動に失敗しました: シェル実行可能ファイル "fish" へのパスが存在しません。」と表示されて何も起きません。おそらく、VSCodeを再起動したことで発生しました。

ターミナル プロセスが起動に失敗しました: シェル実行可能ファイル "fish" へのパスが存在しません。

解決方法

VSCodeのsettings.jsonを開いて、以下のように記述しました。VSCodeの再起動もする必要なく、設定後にシェルが正常に起動します。

    "terminal.integrated.defaultProfile.osx": "fish",
    "terminal.integrated.profiles.osx": {
        "fish": {
            "path": "/usr/local/bin/fish",
        },
    },

osxの部分には各OSに相当するものを入れます。

"fish"の2箇所はおそらくラベルで適当に入れました。 pathには自分が使いたいシェルへのパスを指定します。

原因

VSCode3月のアップデートでシェルのパスの指定方法が変わったことが原因のようでした。 そもそも自分はこれまでシェルのパス指定せずに使ってたので、今後は指定する必要が出てきたのかもしれないです。そうだとしたらちょっと不便ですね。。

code.visualstudio.com

terminal.integrated.defaultProfile.{platform}
terminal.integrated.profiles.{platform}

新しく追加された上記の設定でosごとに細かくシェルの設定ができるようになったみたいです。

参考

【VSCode】 "terminal.integrated.shell" is deprecated.【小ネタ】

VSCodeのターミナルの設定の詳細 - Qiita

macOSで1つのコアだけに負荷を掛けたいけどできない!

&とwaitに関する記事を書いた時、macで1つのコアだけに負荷を掛けたいという状況がありました。しかしどうしても均等に負荷が掛かってしまうので、そのことについてまとめます。なお、この記事からは、原因も解決策もわからないです。

宣伝

dorapon2000.hatenablog.com

目次

環境

やったこと

yesで負荷を掛ける

macOSに収録のyesコマンドはマルチスレッドに最適化されていないため、メニーコアのCPUを積むMacで実行しても、1基の論理CPU(コア)に対し負荷をかけるに過ぎないからだ。

ひたすらMacを重くする「ストレステスト」をお手軽に - 新・OS X ハッキング!(248) | マイナビニュース

私は上の記事を読んで、macのyesであれば1つのコアだけに負荷を掛けられると想像しました。(今あらためて読むと全体として1コア分の負荷を掛けるだけで、1つのコアとは言っていない気もしますが...)

❯ yes > /dev/null &
❯ ps -o pid,comm,%cpu
  PID COMM   %CPU
 6152 -fish   0.0
 8925 -fish   0.0
14652 yes   100.0

yesのcpu使用率は100%ですが、

f:id:dorapon2000:20210612183524p:plain:w400

yes > /dev/null & yes > /dev/null &

f:id:dorapon2000:20210612183555p:plain:w400

1つ実行でも2つ実行でも4つの物理コアに対して均等に負荷が掛かっています。

最近のmacだとなにか仕組みが違うのかと思って別のコマンドでも試してみました。

opensslで負荷を掛ける

yesで紹介した記事の方法にありました。こちらであればCPU数を指定できます。

openssl speed -multi 1

f:id:dorapon2000:20210612184449p:plain:w400

うーん、こちらでも均等に負荷が掛かってしまいます。

openssl speed -multi 8

f:id:dorapon2000:20210612184438p:plain:w400

ちなみに、8を指定すればすべての論理コアに負荷がかかることは確認できます。

stress-ngで負荷を掛ける

stress-ng — Homebrew Formulae

このコマンドであれば、指定したコアにだけ負荷を掛けられるようなので試してみます。

❯ brew install stress-ng

# CPU数1つでコア番号1に指定して負荷を掛ける
❯ stress-ng -c 1 --taskset 1
taskset: setting CPU affinity not supported

エラーが出ました。

結論っぽいもの

エラーについて調べると以下のような記事を見つけました。

stackoverflow.com

どうやら、macAPIではコアを指定してプロセスを実行できないようで、カーネルがすべてのスレッドを掌握しているようです。

となると、それぞれのコアに均等に負荷がかかるのはmacOSがよしなにした結果で、macでは1つのコアだけに負荷を掛けるのは無理そう?

これ以上はブロックボックスっぽいので調べられなさそうです。記事おわり!

シェルの&とwaitについて少し調べてみた

&はシェルにおいてバックグラウンド実行をするコマンド?です。 そして、複数のバックグランドジョブの終了を待つコマンドがwaitです。

つまり、

sleep 20 &
sleep 10 &
wait  # 上2つのコマンド入力直後であれば20秒待機

# sleep 20 & sleep 10 & wait  #1行にもできる

というコマンドは20秒sleepするジョブと10秒sleepするジョブをバックグランドで実行し、それらが終わるまで待つことになります。

今回はmanを見てもよくわからなかった2つの疑問を調べてみます。

疑問

  • 1行に2つ書いた&によるバッググラウンド実行は、ハイパースレッドあるいはマルチコアで実行されるか
  • waitによって完了を待つのは同じ親プロセスに属する&によるプロセスなのか

結論

  • 1行に2つ書いた&によるバッググラウンド実行は、マルチコアで実行される
  • waitによって完了を待つのは、同じ親プロセスに属するすべての&による子孫プロセス

環境

  • WSL2
  • Ubuntu 20.04 LTS
  • bash
  • CPU 4コア4スレッド

調査

疑問に対してそれぞれ調査して確認します。

マルチコアになるか

CPUに負荷をかけるためyesを2つバックグランドで実行します

$ yes > /dev/null & yes > /dev/null &
[1] 2929
[2] 2930

$ jobs
[1]-  Running                 yes > /dev/null &
[2]+  Running                 yes > /dev/null &

$ top
略
%Cpu0  :   0.0/18.2   18[|||||||||||||||||                                                                            ]
%Cpu1  :  62.4/37.6  100[|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||]
%Cpu2  :   0.0/1.1     1[|                                                                                            ]
%Cpu3  :  64.0/36.0  100[|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||]

2つのCPUの利用率が100%になっているため、マルチコアで実行されるということがわかります。

バックグランドジョブの終了

$ kill %1 %2
$ jobs
[1]-  Terminated              yes > /dev/null
[2]+  Terminated              yes > /dev/null

waitが待つプロセス

wait() This function waits for the first child to die. The return value is that of the wait(2) system call.

manの説明ではよくわからなかったので確認します。

叔父プロセス

親プロセスのfishでsleep 100を実行し、その子プロセスのbashでさらにsleep 10sleep 5を実行しました。

sleep 100 &
❯ bash

$ ps
  PID TTY          TIME CMD
   13 pts/1    00:00:06 fish
 3248 pts/1    00:00:00 sleep
 3311 pts/1    00:00:00 bash
 3317 pts/1    00:00:00 ps

$ sleep 10 & sleep 5 & pstree -p 13; wait
[1] 3318
[2] 3319
fish(13)─┬─bash(3311)─┬─pstree(3320)
         │            ├─sleep(3318)
         │            └─sleep(3319)
         └─sleep(3248)
# 10秒後
[1]-  Done                    sleep 10
[2]+  Done                    sleep 5

上の挙動から親のfishで実行されたsleepは待たず、bashで実行された2つのsleepの実行を待っている事がわかります。

ワンライナーではない兄弟プロセス

$ sleep 20 &
[1] 3354

$ sleep 5 & pstree -p 13; wait
[2] 3355
fish(13)───bash(3311)─┬─pstree(3356)
                      ├─sleep(3354)
                      └─sleep(3355)
# 約20秒後
[1]-  Done                    sleep 20
[2]+  Done                    sleep 5

当然ですが、ワンライナーとか関係ないです。同じbashの子プロセスであればすべて待ちます。

甥プロセス

$ sh -c 'sleep 10' & sleep 5 & pstree -p 13; wait
[1] 3358
[2] 3359
fish(13)───bash(3311)─┬─pstree(3360)
                      ├─sh(3358)───sleep(3361)
                      └─sleep(3359)
# 10秒後
[1]-  Done                    sh -c 'sleep 10'
[2]+  Done                    sleep 5

同じ親を持つ子孫すべてのバックグラウンドジョブを待ちました。

まとめ

結論に書いたとおりですが、期待どおりの動作をしてくれました。 package.jsonのscripts内でコマンドを&で並列実行できるか確認したかったのが今回の調査の動機でした。

甥プロセスや叔父プロセスといった名詞があるかは知りません。

HTTP/2のTCPレベルのHoLブロッキングとQUIC

HTTP/2はHTTP/1.1で問題だったHTTPレベルのHoLブロッキングを解消しました。しかし、TCPレベルのHoLブロッキングという問題が依然として残っています。。。。。。🤔🤔

ぐぐるとたくさん解説サイトが出てきますが、いろいろ調べて、自分の言葉に落とし込んでみます。

概要

  • HTTP/1.1にはHTTPレベルのHoLブロッキング問題がある
    • HTTP/2で解決する
  • HTTP/2にはTCPレベルのHoLブロッキング問題がある
    • HTTP/3で解決する
  • HTTP/3ではTCPを使わずに、HTTP/2におけるストリームを別々で管理する

HoLブロッキング とは

f:id:dorapon2000:20210411070906p:plain

HoL (Head of Line) ブロッキングとは、待ち行列ができている中で先頭の客しかサービスを受けられない場合、残りの人が待たされなくてはいけないことを指します。

コンビニで例えるなら、レジにお客さんが並んでいるとして、精算中の人以外の人はスマホをいじりながら待つ必要があります。そんな中、精算中の人がお金が足りないなどと言ってATMに駆け出したら、いらっとくるわけです。このような状況がHTTPでも同じように発生します。

HTTP/1.1

HTTPコネクションを張るということはTCPコネクションを張るということです。ここでの、コネクションという単語はどちらの意味でも取ることができます。

特徴

f:id:dorapon2000:20210411135230j:plain

  • KeepAliveすることにより1つのコネクションで複数のファイルを送信可能。
    • ただし単一コネクション内では並行には送れない(HoLブロッキング
  • コネクションは一般に1つのドメインに対して並列に6つまで張ることができる
    • HTTPの仕様ではなく、一般的なブラウザによる制約
  • 6つ以上張りたい場合はドメインを複数用意する(ドメインシャーディング)
    • 2つのドメインを用意すれば、最大12個のファイルを並列に送信できる

HTTPレベルのHoLブロッキング

1つのHTTPコネクションだけに注目します。

http://www.plantuml.com/plantuml/png/SoWkIImgAStDuKegoYylJYrIqBLJ059bG9QLWhjhY4AYeLv1NZeNI0miMw40Ksc58OSXXTjK8rmwOL8EgNafG7S10000

GET a1.pngGET a2.pngGET c3.png の順に、HTTPリクエストがクライアントの送信キューで順番待ちしているとします。a1.pngのレスポンスがどこかで損失した場合、サーバは再送しますが、その間GET a2.png以降のリクエストを送ることはできません。具体的には、a1.pngは複数のTCPパケットに分割して送られており、そのうちの1つ以上が損失するとこのような状況になります。

HoL=GET a 1.pngが、GET a2.png以降のHTTPリクエストをブロッキングしています。

HTTP/2

HTTP/1.1のコネクションが、HTTP/2ではストリームにあたります。そして、HTTP/2では単一のTCPコネクションで複数ストリームを扱うことで並行にファイルを送信します。

HTTP/2の説明は詳解HTTP/2をとても参考にしています。

特徴

f:id:dorapon2000:20210411170325p:plain

  • 単一のTCPコネクションを使う
    • 1つのTCPパケットに複数ストリームのバイナリが含まれる可能性もある
  • 100以上のストリームでファイルを並行に送信可能
    • 同時に流せる最大ストリーム数はブラウザやサーバによって異なるがapacheは100
    • ファイルごとにストリームが生成される(再利用不可)
    • HTTP/2が出た今、ドメインシャーディングは非推奨*1

TCPレベルのHoLブロッキング

f:id:dorapon2000:20210411170329p:plain

HTTP/2でストリームを謳っても、TCPの上で成り立つ以上、MTUのサイズに整えられたTCPパケットが送信されます。そのパケットが損失すれば、TCPの機能で再送と順序制御がされます。ここがネックなのです。

上図では、ストリーム1だけを含むパケットが損失したため再送されます。その再送パケット以外をブラウザが受け取ったとして、ストリーム2とストリーム3のデータはすべて揃ったのだから、緑とオレンジの画像は表示されると期待してしまいます。

しかし、実際はTCPはシーケンス番号通りの順にしか処理できないため、4つ目のパケットをキューに入れて、3つ目のパケットが来るまで待ちます。キューに入っている間はHTTP側はストリーム3のデータを取り出せないため、オレンジの画像も表示できないです。

HoL=順序が入れ替わったパケットが、後続のパケットの処理をブロッキングしています。

HTTP/2の欠点

ここから見えてくるのは、HTTP/1.1と比べてHTTP/2がパケットロスに弱いという性質です。HTTP/1.1では独立したTCPコネクションを張っていたため、パケット損失はそのコネクションにしか影響がありません。HTTP/2は後続のすべてのストリームに影響があります。

具体的には、2%のパケロスでHTTP/1.1のほうが性能がよくなるようです*2*3。ただ、2%のパケロス環境も相当なようで、現実ではそこまでネックにはならないみたいです。

HTTP/3のQUIC

TCPレベルのHoLブロッキング問題を改善しているのがHTTP/3です。HTTP/3はほぼHTTP/2+QUICの組み合わせを指すため、QUICが肝になります。

QUICの特徴

UDP上で構築されていますが、トランスポート層プロトコルに相当します。

  • TCPと同じような動作をする
    • コネクション志向
    • 再送制御
    • 輻輳制御
  • 複数のストリームを扱うことができ、各ストリームごとに制御することができる
    • アプリケーション層のHTTP/2のストリームの概念をトランスポート層まで落としてきたといえる
    • 特定のストリームのパケット損失は他のストリームに影響がでない

TCPだから問題だったHoLブロッキング問題を、TCPを使わないことで解決しています。大胆ですね汗。

まとめ

L4〜L7のことを調べていると、面白くて無限に文献を漁ってしまいます。QUICはすごいし名前も速そう!

参考文献