Перезапустить службу с помощью зависимых служб?

Начиная с csharp-example и должным образом отмечая связанные вопросы SO (Перезапустить службы Windows от С# и Невозможно перезапустить службу) и различные другие вопросы, связанные с перезапуском только одной службы, мне интересно, какой лучший метод для перезапуска службы с зависимыми службами (например, Message Queuing, от которого зависит Message Queuing Triggers или IIS, от которого зависят FTP Publishing и World Wide Web Publishing). Захват mmc делает это автоматически, но код, похоже, не обеспечивает такую ​​же функциональность (по крайней мере, не так легко).

Документация MSDN для Stop говорит: "Если какие-либо службы зависят от этой службы для их работы, они будут остановлены до остановки этой службы. Свойство DependentServices содержит набор сервисов, которые зависят от этого:" и DependentServices возвращает массив служб. Предполагая, что StartService() и StopService() следуют соглашениям, изложенным в примерах и указанным выше (за исключением того, что они принимают непосредственно ServiceControllers и TimeSpans), я начал с:

public static void RestartServiceWithDependents(ServiceController service, TimeSpan timeout)
{
    ServiceController[] dependentServices = service.DependentServices;

    RestartService(service, timeout); // will stop dependent services, see note below* about timeout...

    foreach (ServiceController dependentService in dependentServices)
    {
        StartService(dependentService, timeout);
    }
}

Но что, если служебные зависимости являются вложенными (рекурсивными) или циклическими (если это возможно...) - если Service A зависит от Service B1 и Service B2 и Service C1 зависит от Service B1, кажется, что "перезагрузка" Service A этим методом остановит Service C1, но не перезапустит его...

Чтобы сделать этот пример более четким, я буду следовать модели в оснастке mmc:

The following system components depend on [Service A]:
  - Service B1
    - Service C1
  - Service B2

Есть ли лучший способ обойти это, или ему просто придется рекурсивно входить и останавливать каждую зависимую службу, а затем перезапускать их после перезапуска основной службы?

Кроме того, будут ли зависимые, но в настоящее время остановленные службы перечислены в DependentServices? Если да, то это не перезапустит их? Если да, то следует ли нам это контролировать? Это просто кажется более беспорядочным и беспорядочным...

* Примечание. Я понимаю, что timeout здесь не применяется полностью (общий тайм-аут может быть много раз длиннее, чем ожидалось), но на данный момент это не проблема, о которой я беспокоюсь - если вы хотите исправьте это, отлично, но не просто говорите, что "тайм-аут сломан..."

Обновление: После некоторого предварительного тестирования я обнаружил (/подтвердил) следующие варианты поведения:

  • Остановка службы (например, Service A), от которой зависят другие службы (например, Service B1), остановит другие службы (включая "вложенные" зависимости, такие как Service C1)
  • DependentServices включает в себя зависимые службы во всех состояниях (Running, Stopped и т.д.), а также включает вложенные зависимости, т.е. Service_A.DependentServices будет содержать {Service B1, Service C1, Service B2} (в этом порядке, поскольку C1 зависит от B1).
  • Запуск службы, которая зависит от других (например, Service B1 зависит от Service A), также запустит необходимые услуги.

Таким образом, приведенный выше код может быть упрощен (по крайней мере частично), чтобы просто остановить основную службу (которая остановит все зависимые службы), а затем перезапустить наиболее зависимые службы (например, Service C1 и Service B2) (или просто перезапустив "все" зависимые сервисы - он пропустит уже запущенные), но это действительно просто мгновенно отменяет запуск основной службы, пока одна из зависимостей не пожалуется на нее, так что это действительно не помогает.

Ищите теперь, как только перезапуск всех зависимостей является самым простым способом, но который игнорирует (на данный момент) управление уже остановленными службами и такими...

Ответ 1

Хорошо, наконец, это реализовано. Я опубликовал его как отдельный ответ, поскольку я уже пришел к такому выводу в исходном обновлении моего вопроса, который был опубликован до первого ответа.

Опять же, методы StartService(), StopService() и RestartService() следуют соглашениям, изложенным в примерах и уже упомянутым в самом вопросе (т.е. они обертывают поведение Start/Stop, чтобы избежать "уже начатого/остановленного" - тип исключения) с добавлением, которое, если a Service передано (как это имеет место ниже), Refresh() вызывается в этой службе, прежде чем проверять его Status.

public static void RestartServiceWithDependents(ServiceController service, TimeSpan timeout)
{
    int tickCount1 = Environment.TickCount; // record when the task started

    // Get a list of all services that depend on this one (including nested
    //  dependencies)
    ServiceController[] dependentServices = service.DependentServices;

    // Restart the base service - will stop dependent services first
    RestartService(service, timeout);

    // Restore dependent services to their previous state - works because no
    //  Refresh() has taken place on this collection, so while the dependent
    //  services themselves may have been stopped in the meantime, their
    //  previous state is preserved in the collection.
    foreach (ServiceController dependentService in dependentServices)
    {
        // record when the previous task "ended"
        int tickCount2 = Environment.TickCount;
        // update remaining timeout
        timeout.Subtract(TimeSpan.FromMilliseconds(tickCount2 - tickCount1));
        // update task start time
        tickCount1 = tickCount2;
        switch (dependentService.Status)
        {
            case ServiceControllerStatus.Stopped:
            case ServiceControllerStatus.StopPending:
                // This Stop/StopPending section isn't really necessary in this
                //  case as it doesn't *do* anything, but it included for
                //  completeness & to make the code easier to understand...
                break;
            case ServiceControllerStatus.Running:
            case ServiceControllerStatus.StartPending:
                StartService(dependentService, timeout);
                break;
            case ServiceControllerStatus.Paused:
            case ServiceControllerStatus.PausePending:
                StartService(dependentService, timeout);
                // I don't "wait" here for pause, but you can if you want to...
                dependentService.Pause();
                break;
        }
    }
}

Ответ 2

Похоже, вы хотите перезапустить "базовую" службу, и все то, что полагается на нее, также перезапускается. Если это так, вы не можете просто перезапустить все зависимые службы, потому что они, возможно, не выполнялись заранее. Нет API для этого, о котором я знаю.

То, как я это сделаю, это просто написать рекурсивную функцию для сканирования всех зависимых сервисов (и их зависимостей) и добавить все службы, которые были запущены в список по порядку.

Когда вы перезапустите базовую услугу, вы можете просто запустить этот список и запустить все. Если вы не пересортировали список, службы должны начинаться в правильном порядке, и все будет хорошо.

Ответ 3

Обратите внимание, что ServiceController.Stop() останавливает "зависимые" службы и ServiceController.Start() начинает "в зависимости от" сервисов ", поэтому после остановки службы вам нужно только запустить службы, у которых есть дерево зависимостей.

Предполагая, что циклические зависимости не разрешены, следующий код получает службы, которые необходимо запустить:

    private static void FillDependencyTreeLeaves(ServiceController controller, List<ServiceController> controllers)
    {
        bool dependencyAdded = false;
        foreach (ServiceController dependency in controller.DependentServices)
        {
            ServiceControllerStatus status = dependency.Status;
            // add only those that are actually running
            if (status != ServiceControllerStatus.Stopped && status != ServiceControllerStatus.StopPending)
            {
                dependencyAdded = true;
                FillDependencyTreeLeaves(dependency, controllers);
            }
        }
        // if no dependency has been added, the service is dependency tree leaf
        if (!dependencyAdded && !controllers.Contains(controller))
        {
            controllers.Add(controller);
        }
    }

И с помощью простого метода (например, метода расширения):

    public static void Restart(this ServiceController controller)
    {
        List<ServiceController> dependencies = new List<ServiceController>();
        FillDependencyTreeLeaves(controller, dependencies);
        controller.Stop();
        controller.WaitForStatus(ServiceControllerStatus.Stopped);
        foreach (ServiceController dependency in dependencies)
        {
            dependency.Start();
            dependency.WaitForStatus(ServiceControllerStatus.Running);
        }
    }

Вы можете просто перезапустить службу:

    using (ServiceController controller = new ServiceController("winmgmt"))
    {
        controller.Restart();
    }

Достопримечательности:

Для ясности кода я не добавил:

  • таймауты
  • проверка ошибок

Обратите внимание, что приложение может оказаться в странном состоянии, когда некоторые службы перезапущены, а некоторые не...