Skip to content

SAIF.NetCore.WindowsService operations and processes

This page is the operation catalog and cross-operation hub for this library. Each operation's detailed, step-by-step logic lives in its own page under operations/.

Operation catalog

Operation Intent Trigger Inputs Outputs Rules
Run the Windows Service program Build (once) and run the Generic Host, blocking until shutdown Consumer's Main calls new Program().Run(args) Command-line args Blocks until host shutdown; sets static ServiceHost WS-BR-01
Create and configure the host Build the host, apply the optional startup delay, and run the error-handler/logger configuration hooks OP-01 step 1, or a direct consumer call args A built, configured IHost, stored in static ServiceHost WS-BR-02, WS-BR-04, WS-BR-05
Create the default host builder Assemble the IHostBuilder with the configuration-source stack and Windows Service hosting OP-02 step 1 args IHostBuilder WS-BR-03
Construct a service worker Build a ServiceWorker<T> with its task runner and error handler DI activation of the consumer's ServiceWorker<T> subclass IServiceTaskRunner, IWindowsServiceErrorHandler A worker with TaskRunner/ErrorHandler set and default timing properties WS-BR-13
Execute the worker loop Run the task repeatedly, with a fixed delay after each run, until cancellation BackgroundService.StartAsync during IHost.Run() stoppingToken Completes on cancellation WS-BR-06, WS-BR-07
Run one task iteration Invoke the consumer's task and route any exception to error handling OP-05 step 2 stoppingToken None on success; task-runner exceptions are caught and delegated to the error handler, which may itself throw and propagate out of the operation WS-BR-08
Handle a task error Decide shutdown exemption, log the error, and stop the host unless exempt OP-06 step 2 The exception A logged error; host stop unless the exception is exempt WS-BR-09, WS-BR-10, WS-BR-11, WS-BR-12

Process flow diagram

The following diagram visualizes the primary operational flow across all seven operations.

flowchart TD
    Start([Consumer Main calls Run]) --> HostNull{ServiceHost == null?}
    HostNull -->|Yes| Create[Create and configure the host]
    HostNull -->|No| RunHost
    Create --> Builder[Create the default host builder]
    Builder --> Delay[Delay startup if DelayStart set]
    Delay --> Hooks["Configure: error-handler hook, then logger"]
    Hooks --> RunHost[ServiceHost.Run]
    RunHost --> WorkerCtor[Construct a service worker]
    WorkerCtor --> Loop[Execute the worker loop]
    Loop --> Iter[Run one task iteration]
    Iter --> Err{Exception thrown?}
    Err -->|No| WaitDelay[Task.Delay TaskDelayMilliseconds]
    Err -->|Yes| Handle[Handle a task error]
    Handle --> Exempt{Shutdown-exempt?}
    Exempt -->|Yes| WaitDelay
    Exempt -->|No| Stop([ServiceHost.StopAsync])
    WaitDelay --> Cancelled{Cancellation requested?}
    Cancelled -->|No| Iter
    Cancelled -->|Yes| Done([Loop exits])

Failure and edge-case behavior

  • A missing IWindowsServiceErrorHandler, ISaifWindowsServiceLogService, IEnvironmentClient, or IHttpJsonClient registration throws InvalidOperationException during Create and configure the host, before the host runs (WS-BR-05).
  • A non-exempt task error allows ShutdownTimeoutSeconds (default 30) for graceful stop; the code verifiably calls ServiceHost.StopAsync(ShutdownTimeoutSeconds), which links that caller-supplied timeout with the host's own, independently configured HostOptions.ShutdownTimeout into a single token, then visits every registered hosted service (via Host.StopAsync) and calls each one's StopAsync. Only a hosted service that is actually a BackgroundService cancels its own stopping token and returns as soon as either that service's own ExecuteAsync task completes or the linked token is cancelled -- which happens when either budget (ShutdownTimeoutSeconds or HostOptions.ShutdownTimeout) elapses first; an arbitrary IHostedService need not implement this pattern at all. For the worker whose own task error triggered this call, its ExecuteTask is the very task suspended on this call chain (ExecuteAsync → RunTaskAsync → HandleTaskError → StopAsync), so that worker's own wait can only resolve once the linked token is cancelled, not by its task completing first* -- and only once its execute task has actually been assigned; a failure before StartAsync sets that task is not subject to this wait. Other hosted services registered in the same host (or this worker during an externally initiated shutdown while idle in Task.Delay) are not subject to that same self-referential wait and can complete sooner once their stopping token is cancelled. Whether other hosted services registered alongside this worker extend the overall shutdown sequence further, and the resulting total duration, is unknown without inspecting the host's full service registration and configured HostOptions** (see Handle a task error and open question OQ-02 below).
  • An exception thrown anywhere inside the error-handling path itself — predicate evaluation, message construction, ErrorHandler.HandleError, or the StopAsync call — is not absorbed; it propagates out of Run one task iteration and out of the worker's ExecuteAsync, skipping the intended shutdown.
  • A ServiceWorker<T> hosted by anything other than ServiceProgramRunnerBase fails with NullReferenceException on its first non-exempt error, because ServiceHost is a static reference owned by ServiceProgramRunnerBase.
  • The re-added appsettings.json/environment-variable providers invert standard .NET configuration precedence; see Configuration for the full consequence list.

Open questions

  • OQ-02: BackgroundService.StopAsync's own-task-vs-linked-token wait means the worker whose task error triggered the shutdown can only unblock once the linked shutdown token is cancelled -- by whichever of the caller's ShutdownTimeoutSeconds or the host's own HostOptions.ShutdownTimeout elapses first -- not by its own ExecuteTask completing first (confirmed from the inherited BackgroundService/Host source). What is not confirmed is the end-to-end duration and ordering effect of other hosted services registered in the same host during Host.StopAsync's sequential visit, nor the configured value of HostOptions.ShutdownTimeout in any specific deployment -- that would need a host-level test against a concrete host configuration to observe*.

Note: Statements marked with * were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts. Statements marked with ** could not be confirmed; treat as unknown until verified.

Sources

  • src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService.csproj
  • src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceProgramRunnerBase.cs
  • src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceWorker.cs