服务在被频繁杀死的情况下重新启动服务

服务在被频繁杀死的情况下重新启动服务

当年在写一个服务监控程序的时候,遇到一个问题,被监控程序在被快速频繁杀死的情况下,有一定的概率无法被拉起。当时这个问题在项目中存在已经很久了,横跨多个版本,原来是通过增加超时时间以及重新尝试次数来解决,未能从根本上解决该问题。当时事情也很多,只能靠每天下班以后的时间来查问题,过了一星期终于解决了。

解决这个问题的过程中和同事发生了很多次激烈的碰撞,写下这个算作是以此为戒吧。

收获如下:

1.对于有权限设定的函数,需要设置所需要的最小权限,而不是使用所有权限。

2.对于一个监控程序,作且仅作一件事情:感知到被监控程序的运行状态,仅在被监控程序异常死亡时拉起。 不应该与业务逻辑纠缠在一起,负责启动被监控程序。

3.多倾听别人的意见,如果没有读书,没有去研究就要虚心听别人的想法,不要什么也不知道就讨论。

4.改动一个使用很久的模块,比修改新增加模块更耗时,需要考虑得更全面,承担风险,还要合理沟通,说明改动原因,不能想当然的认为这样的改动更好。

/**
 * @brief 启动指定服务,如果服务已经启动则返回TRUE
 * @details
 * 1.由于该函数需要在被监控服务频繁异常退出的场景下调用,鉴于服务的响应机制,需要
 *   考虑函数内调用的API是否具有即时性。
 * 2.由于Windows服务机制,API:QueryServiceStatus(Ex)获取结果可能不及时导致对服务
 *   状态误判因此只能使用ControlService获取状态。
 * 3.服务启动过程中会建立PIPE与WSCM的通讯,在极端情况下服务异常退出再启动过程中可能
 *   会经历:
 *   (1)ERROR_BROKEN_PIPE:109, The pipe has been ended.
 *   (2)ERROR_BAD_PIPE:230, The pipe state is invalid.
 *   (3)ERROR_SERVICE_NOT_ACTIVE:1062, The service has not been started.
 *   因此需要尝试多次启动服务
 * @param [in]lpszSvcName  服务名称
 * @return FALSE表示启动失败,TRUE表示启动成功或服务已经启动
 */
BOOL StartService(LPCTSTR lpszSvcName)
{
    assert(lpszSvcName);

    BOOL bRet = FALSE;
    SC_HANDLE schSCManager = NULL;
    SC_HANDLE schService   = NULL;
    SERVICE_STATUS ssStatus = {0};

    // 获得SCManager句柄 
    schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
    if (NULL == schSCManager)
    {
        goto cleanup;
    }

    // 获得服务句柄
    schService = OpenService(schSCManager, lpszSvcName,
        SERVICE_QUERY_STATUS | SERVICE_START | SERVICE_INTERROGATE);
    if (NULL == schService)
    {
        goto cleanup;
    }

    // 启动服务(在StartService前先调用一下ControlService可以更新WSCM中的服务状态)
    bRet = ControlService(schService, SERVICE_CONTROL_INTERROGATE, &ssStatus);
    do 
    {
        bRet = ::StartService(schService, 0, NULL);
        bRet = ControlService(schService, SERVICE_CONTROL_INTERROGATE, &ssStatus);
        // 绝大多数情况下可以一次启动服务,极端情况下最多三次可以完成启动服务
        const UINT unPreWait = 100U;
        Sleep(unPreWait);
    } while (!bRet);

    bRet = (SERVICE_RUNNING == ssStatus.dwCurrentState ||
            SERVICE_START_PENDING == ssStatus.dwCurrentState);
cleanup:
    if (schService)
    {
        (VOID)CloseServiceHandle(schService);
    }
    if (schSCManager)
    {
        (VOID)CloseServiceHandle(schSCManager);
    }
    return bRet;
}