しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

HttpPlatformHandler に追加された processesPerApplication を検証してみた

HttpPlatformHandler に新しく追加された processesPerApplication の挙動が気になったので簡単に検証してみました。動かすアプリとしては手軽な Go を選びました。

テンプレ的なコードですが、Martini を使った最低限のサーバーを用意します。

package main

import "github.com/go-martini/martini"

func main() {
  m := martini.Classic()
  m.Get("/", func() string {
    return "Hello world!"
  })
  m.Run()
}

そして Web.config は以下のようなものを用意しておきました。processesPerApplication の値としては、特に意味はないのですが 3 にしておきます。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <handlers>
      <add name="httpPlatformHandlerMain" modules="httpPlatformHandler" path="*" verb="*" resourceType="Unspecified" />
    </handlers>
    <httpPlatform processPath="C:\Go\bin\go.exe" arguments="run C:\inetpub\wwwroot\server.go" processesPerApplication="3">
      <environmentVariables>
        <environmentVariable name="PORT" value="%HTTP_PLATFORM_PORT%" />
      </environmentVariables>
    </httpPlatform>
  </system.webServer>
</configuration>

これでサクッと動くと思ったんですが、残念ながら HTTP_PLATFORM_PORT を environmentVariable で展開した時に、謎のスペースが追加される問題が直っていませんでした。

[martini] listening on :31408                (development)
[martini] listen tcp: GetAddrInfoW: The specified class was not found.
exit status 1

既に MSDN Forum で次のリリースで直すよ的なことを聞いていたんですが、このバージョンには修正が入らなかったようで残念です。*1

仕方ないのでサーバーのコードを変更して、前回と同様に HTTP_PLATFORM_PORT を直接見ます。

package main

import (
  "github.com/go-martini/martini"
  "os"
)

func main() {
  m := martini.Classic()
  m.Get("/", func() string {
    return "Hello Martini!"
  })
  m.RunOnAddr(":" + os.Getenv("HTTP_PLATFORM_PORT"))
}

これで無事にサーバーは起動するようになったので、Process Explorer でどのようにサーバーのプロセスが立ち上がっているのか確認します。

f:id:shiba-yan:20150128210608p:plain

予想としては初回のリクエスト時に設定した数のプロセス、つまり今回はプロセス 3 つが立ち上がるのかと思っていましたが、1 つだけ立ち上がるという結果になりました。

そしてこの状態でページをリロードしてみると、プロセスがもう 1 つ立ち上がりました。

f:id:shiba-yan:20150128210917p:plain

もう一度リロードすると更に 1 つ立ち上がり、その後は増えることはありませんでした。processesPerApplication で指定した数になると、それ以降はプロセスを使いまわす実装のようです。

f:id:shiba-yan:20150128204948p:plain

Always On を有効にしてプリロードを行った場合にどのようになるのか気になりますが、とりあえずこの状態でリクエストがどのように各プロセスに振り分けられるのか確認します。

そのためにサーバーを少し修正して、プロセス ID を出力するようにしました。

package main

import (
  "github.com/go-martini/martini"
  "os"
  "strconv"
)

func main() {
  m := martini.Classic()
  m.Get("/", func() string {
    return "Hello Martini! - pid:" + strconv.Itoa(os.Getpid())
  })
  m.RunOnAddr(":" + os.Getenv("HTTP_PLATFORM_PORT"))
}

結果としては iisnode と同じようにラウンドロビンとして実装されているようです。

おまけ:プロセスを殺した時

サーバーのプロセスが落ちた時の挙動を調べるために Process Explorer で適当にプロセスを殺してみると、HttpPlatformHandler が自動的に processesPerApplication で指定した数になるまで起動してくれました。

FastCGI のようにメモリ使用量やリクエスト処理件数が設定を超えた時に、プロセスを再起動する機能があってもいいかなと思いました。