外部サービスに依存した処理をPC起動直後に呼び出す場合はサービス起動タイミングによって失敗する可能性を考慮する必要がある

みなさんごきげんよう。ういこです。私事ですが顔面半分にビッシリと赤い発疹がでて、洒落じゃなく顔面崩壊してますが皆様はいかがお過ごしでしょうか。本日は 「外部サービスに依存した処理(しかも WMI みたいな重い処理なんてとくに)は PC 起動直後のログオン時に動作するプログラム(ログオンスクリプトやサービス アプリケーション)内で使うこととあまり相性が良くない」 というお話です。

今日、お隣のチームさんからスクリプトである機能を自動化したいとご相談をを受けまして、要件を見たら、なーんだ WMI つかえば簡単じゃないのよ奥様?これなら有りモノで何とかなるんじゃないの?WMI ってほんと調理に便利よね★ とふっと思ったんですが、WMI って RPC に依存しているんですよね。

ということは…PC 起動 → ログオン直後にスクリプト実行というタイミングでは、WMI サービス起動のタイミングがログオンスクリプトの動作タイミングに間に合わないことがあり得るわけです。
最近のマシンはメモリもハードディスクも脳みそ (CPU) も充実したリア充が多いので顕在化しないで済むことがほとんどだと思います。しかし、有りモノのマシンだってまだまだ現役!という環境とか、大量のサービスが動作するような環境では、この「起動タイミングによって残念」という状況も起こりえてしまいます。その状況をクリティカルととらえるか、あるいはまた動作させればいいじゃないか、で済むかはそれぞれのシステム要件次第ですが、おおむね起きることはないから大丈夫ということでも、ミッションクリティカルなシステム上で動かす場合や、サービス アプリケーションの場合などは、避けるべきと私は考えます。もし、今ログオン時に動作させるプログラムを設計されているならば、ぜひこの点について考慮の上プログラム設計とシステム要件を検討されればと思います。

今回は、デベロッパーサポート担当らしく、サービス アプリケーション作成を例にとりご案内したいと思います。

[1] サービスの起動順序は必ずしも人間の期待通りになるとは限らない
あるサービスに依存しているサービスが自動起動になっている場合、依存先のサービスがあがったあとに自身のサービスが起動される動きになります。たとえば、WMI の場合、後述のように RPC に依存していることがわかります。さらに、RPC を見ると、RPC 自身も別のサービスに依存しています。

では、サービスの起動順序はどのようにマネージされているかですが、自動起動となっているサービスは、HKLM\SYSTEM\CurrentControlSet\Control\ServiceGroupOrder\List に格納されている順にフェーズ単位で起動されていきます。このレジストリ値に格納される順番がイコール起動順序となり、この順番で起動されていきます。よってレジストリを変更する事によって、起動順序を変更する事は可能です。
しかし、サービスの起動順序の変更をするということは、数十以上ある全サービスの起動順序に大きく影響を及ぼす可能性があるということを意識する必要があります。基本的にはサービス起動順序を変更することはお勧めしません。どうしても変更することが必要である場合は、可能な限り考えうる「重い」環境でじっくりと検証を実施することをお勧めいたします。また、可能であればですがそうしたサービスを用いない API などで処理ができないか検討することもよい方法です。

[2] 問題としてよくあげられる例 - サービス アプリケーションを作ったが動かない
これまで挙げられた例としては、ログオン スクリプトなどのほか、サービス アプリケーションを作成していて、このサービスの起動時に依存先サービスを使った処理を実施していると制御が返ってこない、または極端にレスポンスが悪くなることがあるというものがあります。現象の出方はタイミングによるので様々ですが、例えば処理が遅い、依存サービスのタイムアウトに間に合わず起動失敗するなどの状況などがあります。これらの条件を満たし、かつイベント ID 7009 などがイベントログに記録されている場合はこれにあたる可能性が高いです。

サービスの所属グループはどこ?サービスの依存関係は変更できるの?
通常、サービスの起動および停止順序は、サービス単体の依存関係、サービスグループの依存関係で制御されています。サービスグループの起動される順番は下記のレジストリキーにおける List 値により確認する事が出来ます。 なお、グループに所属していないサービスもありますが、これは順番を変えることはできません。

HKEY_LOCAL_MACHINE
\SYSTEM
\CurrentControlSet
\Control
\ServiceGroupOrder

どのサービスがどのグループに所属しているかは、[管理ツール] - [コンピュータの管理] - [サービス] で、各サービスをダブルクリックすることで 確認できます。

image

図1 : WMI (Windows Management Instrumentation) のプロパティより。RPC に依存している状況がわかる。

image

図 2 : RPC (Remote Procedure Call) のプロパティ。さらに DCOM 関連サービスなどに依存している。ちなみにここからも、WMI が RPC の依存している DCOM にさらに依存していることが見て取れる。

また、依存関係にあるサービスも確認することも、依存関係を設定することも、下記のレジストリ DependOnService から可能です。

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\<自分の作っているサービス アプリケーション名>

1. [Start] - [Run] をクリックします。
2. 名前に "regedit" と入力し、レジストリ エディタを起動します。
3. [編集] メニューの [値の追加] をクリックして、次の情報を入力します。

Value Name : DependOnService
Value Type : REG_MULTI_SZ
Data : 依存するサービス名

イベント ID 7009 について
イベント ID 7009 は、サービス プロセスの管理およびサービス プロセスの起動や終了を制御する Service Control Manager (SCM) がサービスを起動させる際のタイムアウト値 (既定では 30000 ミリ秒) を超えた場合に記録されます。つまり、サービスから SCM に対して、"サービスが開始した"(SERVICE_RUNNING)ことを通知していない場合に、ID 7009 が記録されます。障害ではない状況でもこのイベントは発生しうる可能性はあります。例えば、Microsoft Update による修正プログラム適用やデバイス ドライバの更新などが発生した場合、システム内部情報の更新や整合性を保つための処理が行われることがあり、適用後の初回起動時に、通常の再起動時とは異なり、サービスの開始に時間を要する結果発生する場合などもあります。これは何かサービスを利用するアプリケーション (あるいはサービス) を実装する際、この現象が起きうる可能性を考慮し設計する必要があるということを意味します。

レジストリによる SCM 遅延時間変更については以下のサポート技術情報をご覧いただくともっとわかりやすいと思います。

Windows Server 2003 でサービスが開始されずイベント 7000 および 7011 が記録される
https://support.microsoft.com/kb/922918

変更方法 :

1. [Start] - [Run] をクリックします。
2. 名前に "regedit" と入力し、レジストリ エディタを起動します。
3. 下記のパスのキーをアクティブにし、値を追加します。下記を右クリックし、[NEW] - [DWORD Value] をクリックします。

HKEY_LOCAL_MACHINE
\SYSTEM
\CurrentControlSet
\Control

4. 新しく作成された値を下記のように設定します。

Name : ServicesPipeTimeout

例:60秒に設定する場合(Decimal の場合はミリ秒で設定)

60000 (Decimal)

[3] 回避策 : サービス アプリケーションの実装で回避する
WMI に限らずサービス アプリケーションで自身のサービス起動時に 重い処理を実施する必要がある場合は、サービスのメイン スレッドとは別にワーカー スレッドを起動させ、ワーカー スレッド内で時間を要する可能性がある処理を実施し、メイン スレッドで Service Control Manager (SCM) に対し SERVICE_RUNNING ステータス通知を行う実装が一般的です。あるいは上記レジストリ設定あるいはご自身で作成されているサービスのスタートアップの起動の種類を遅延にしてタイムアウトの間隔を変更しても、状況によっては設定した値を上回る時間を処理に要することになる可能性などもあるため、やはりワーカースレッド内で時間を要する処理を実施頂く設計にすることが最も確実なリスクヘッジの手段であるといえます。
特に、WMI は非常に重い処理であるため、特にシステム負荷が高い起動時に利用した場合特に遅くなる可能性は正直あります。もともと WMI 自体がそもそも完了までに時間がかかる可能性のある処理であるため、処理時間を短縮することは非常に困難です。

上述の SCM がサービスを監視する際のタイムアウト時間内に SERVICE_RUNNING ステータスを通知されない場合は ID 7009 などが発生しますので、SCM からこのサービスに対し停止リクエストが送信されるなどのリスクが発生します。
よって、完了までにどのぐらいの時間を要するか判断ができない処理を、SERVICE_RUNNING ステータス通知を行う前の段階で実行するのではなく、サービスが SERVICE_RUNNING ステータスを通知した後でワーカスレッドを起動し、このワーカスレッド内で
時間のかかることが予想される処理を実行していただくことが適切となります。これにより、上述のようにシステム コンディションによって時間が変動しうる可能性がある機能に依存するサービスであっても安定して動作させることが期待できます。

How to debug Windows services
https://support.microsoft.com/default.aspx?scid=824344

[4] まとめ
・サービスの起動および停止順序は、サービス単体の依存関係、サービスグループの依存関係で制御されている
・サービスが自動起動の際はレジストリに追加された順番を起動順序として順次実行されていく
・依存関係先サービスが多いサービスの処理を自身のプログラム中で、PC 起動直後やログオン時に参照するような処理を実施している場合は起動タイミングによっては参照先サービス起動が間に合わず自身のプログラムが影響され動作しない場合がある
・外部サービスの処理を参照するような処理はせず、API などで代替できる処理がないか確認することも有効手段の一つ
・やむを得ず自身のサービスから外部サービスの処理を参照する必要がある場合は、サービスが SERVICE_RUNNING ステータスを通知した後でワーカスレッドを起動し、ワーカスレッド内で時間のかかることが予想される処理を実行することが安全な設計となる

それでは皆様ごきげんよう。

ういこう@呪われてる?心当たりありすぎてわからないです。