none
SetServiceStatus でのサービス終了時のタイムアウト値更新について RRS feed

  • 質問

  • はじめまして。

    サービスプログラムを作成しているのですが、その終了に数分の時間がかかる場合があるため、 SetServiceStatus  関数を用いてタイムアウトが発生しないようにしたいと考えています。

    そこで次のように処理をすれば対応できるのではないかと考えました。

    1)ハンドラがSCMから終了依頼(SERVICE_CONTROL_STOP) を受け取る
    2) ループに入る
    3) 5秒間のWaitForSingleObjectを実行。
    4-1) タイムアウトした場合: SetServiceStatusで SERVICE_STOP_PENDINGで 5秒の待ち時間を追加し、2)に戻る
    4-2) イベント発生時: SetServiceStatus で SERVICE_STOPPED を通知し、ループを抜ける。

    これを実現できるようにコードを書いたつもりなのですが、処理は正常に行われるように見えるのですが、
    1分後には急にログが出力されなくなります。
    どうやら SCM によりサービスアプリが強制終了されているようです。

    何か処理を間違っているのでしょうが、サッパリわからず困っています。
    もし、お気付きの点があれば教えてください。

    よろしくお願いします。

    上記のハンドラのコードを添付します。
    # 以下のコードはサンプルで、終了までの時間を100秒固定としています。
    # 残り45秒のログが出力されたところで、ログ出力が停止されます。
    # sc query <サービス名> で確認するとステータスがSTOPPEDとなっているため、
    # 強制終了されたのではないかと考えています。

    // ======================================
    // ハンドラ関数
    DWORD WINAPI HandlerEx (
        DWORD dwControl,
        DWORD dwEventType,
        LPVOID lpEventData,
        LPVOID lpContext )
    {
    
         SERVICE_STATUS ss;
         BOOL bRet;
         
        // Initialize Variables for Service Control
        ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
        ss.dwWin32ExitCode = NO_ERROR;
        ss.dwServiceSpecificExitCode = 0;
        ss.dwCheckPoint = 0;
        ss.dwWaitHint = 0;
        ss.dwControlsAccepted = 0;
    
        int wait = 100 ;
        BOOL ret = TRUE;
    
        switch(dwControl) {
    
        case SERVICE_CONTROL_STOP:
            // Set STOP_PENDING status.
            ss.dwCurrentState = SERVICE_STOP_PENDING;
            bRet = SetServiceStatus (serviceHandle, &ss);
    
            ss.dwCheckPoint = 0;
            ss.dwCurrentState = SERVICE_STOP_PENDING;
            ss.dwControlsAccepted = 0;
            ss.dwWaitHint = 5 * 1000; 
    
           while ( Sleep(5000),(wait-=5) > 0 ) // この部分はサンプルとして改変しています。
            {
                    bRet = SetServiceStatus (serviceHandle, &ss);
                    ss.dwCheckPoint ++ ;
                    {
                 WCHAR buff[255] ;
                 wsprintf(buff, L"timeout... remain=%d chkPnt=%d",
                     wait,
                     ss.dwCheckPoint);
                 OutputDebugString(buff);
             }
    } // Set STOPPED status. ss.dwCurrentState = SERVICE_STOPPED; ss.dwCheckPoint = 0; ss.dwWaitHint = 0; bRet = SetServiceStatus (serviceHandle, &ss); if (!bRet) { DebugPrint (TEXT("SetServiceStatus failed.\n")); break; } break; default: return ERROR_CALL_NOT_IMPLEMENTED; } return NO_ERROR; }

    2014年7月16日 12:04

回答

  • 失礼しました。

    こちらの誤解でした。申しわけありません。

    なお、こちらの課題は、以下の2点により解決しましたので、ご報告いたします。
    1) 別スレッドでSetServiceStatus() を行うことで、強制終了されなくなった。
    2) ChangeServiceConfig2 により PreShutdownの時間をサービス起動時に延長しておくことで、強制終了されなくなった。

    アドバイスをいただいた佐祐理さん、ありがとうございました。




    • 回答としてマーク t_smz 2014年7月25日 2:40
    2014年7月25日 2:39

すべての返信

  • HandlerExには

    The control handler should return as quickly as possible; if it does not return within 30 seconds, the SCM returns an error.

    と書かれていますが。停止に時間がかかっているのではなく、Handlerがハングアップしたと見做されたのかもしれません。

    2014年7月16日 12:53
  • 佐祐里さん
    ありがとうございます。

    なるほど。それが原因でしょうか。

    引用いただいた文章を見ますと、続けて次の記述があります。

    If a service must do lengthy processing when the service is executing the control handler, it should create a secondary thread to perform the lengthy processing, and then return from the control handler.

    ということは、上記のようにループを組みたい場合は別スレッドにする必要があるということでしょうか。
    一度トライしてみます。
    2014年7月17日 0:01
  • トライしてみました。

    ループ部分を別関数化し、それをCreateThreadで呼び出す形としました。

    すると、上で書いたようなエラーは発生しなくなりました。

    標準ツールの「サービス」では停止中にツール側のタイムアウトと思われるエラーが発生しますが、
    この場合もsc コマンドで確認すると、ちゃんとSTOP_PENDING のままで遷移しており、問題はなさそうです。

    ただし、Preshutdown時に時間がかかる点については、同様の手法ではダメなようです。

    こちらについては ChangeServiceConfig2 で設定値を変更するしか時間を稼ぐ方法はないものでしょうか?

    2014年7月17日 4:23
  • ループ部分を別関数化し、それをCreateThreadで呼び出す形としました。すると、上で書いたようなエラーは発生しなくなりました。

    蛇足ながら、本来であれば停止処理があり、その処理内でSetServiceStatus()すべきです。CreateThread()したのはあくまで動作確認のためですよね?

    ただし、Preshutdown時に時間がかかる点については、同様の手法ではダメなようです。こちらについては ChangeServiceConfig2 で設定値を変更するしか時間を稼ぐ方法はないものでしょうか?

    そのためのShutdownとは別に用意されたPreshutdownですし。ChangeServiceConfig2()するのが正しい方法かと。

    とはいえ、停止に何十秒も要する設計についても見直した方がいいかと思います…。

    2014年7月17日 12:49
  • 佐祐理さん
    何度もありがとうございます。

    佐祐理さんのご指摘では、Handler内でSetServiceStatus() すべきとのことですが、
    当方が引用した通りHandlerExの解説に、「時間がかかる場合には別スレッドを起こして
    そちらで終了待ちをさせ、Handlerからはサッサと制御を戻せ」と書いて(あると思って)います。

    実際にHanlder内でループを回すと最初の質問の通り強制終了されてしまいますので、別
    スレッドでの終了待ちは、ドキュメントにも従った正当な手順だと考えていました。

    佐祐理さんが別スレッド化が好ましくないとお考えの理由は何でしょうか?

    余談ですが、そもそも時間がかかってしまうのが問題というのはおっしゃる通りで、異論ありません。

    ただ、今回の問題はPCに接続されている外部ハードウェアの終了処理に時間がかかる点にあり、
    フェイルセーフの観点からも、コントローラであるPC側でも考慮が必要となるため、このような質問
    をさせていただいています。

    2014年7月22日 4:49
  • そうは言っていません。「本来であれば停止処理があり、その処理内でSetServiceStatus()すべきです」と書きました。

    2014年7月24日 23:14
  • 失礼しました。

    こちらの誤解でした。申しわけありません。

    なお、こちらの課題は、以下の2点により解決しましたので、ご報告いたします。
    1) 別スレッドでSetServiceStatus() を行うことで、強制終了されなくなった。
    2) ChangeServiceConfig2 により PreShutdownの時間をサービス起動時に延長しておくことで、強制終了されなくなった。

    アドバイスをいただいた佐祐理さん、ありがとうございました。




    • 回答としてマーク t_smz 2014年7月25日 2:40
    2014年7月25日 2:39