#c_sharp #net #windows_service
Есть относительно критический Windows Service, который должне гарантированно запускаться в случае перезагрузки системы. Проблема в том, что система после перезагрузки (точнее, пересоздания VM) достаточно сильно тормозит, и старт сервиса падает с ошибкой: The ServiceName service failed to start due to the following error: The service did not respond to the start or control request in a timely fashion. Cудя по логам, код в OnStart у сервиса не вызывается, так что запросить дополнительное время вызовом ServiceBase.RequestAdditionalTime не получается. Сервис падает, даже если OnStart пустой. Судя по всему, инициализация CLR просто не вписывается в стандартный таймаут. При этом сервис нормально и быстро стартует на уже загрузившейся системе. Настройки Recovery в свойствах сервиса не помогают - они применяются только в случае, если сервис упал уже после успешного запуска. Есть ли простой/стандартный/удобный способ гарантировать старт windows service в условиях тормозов системы при старте?
Ответы
Ответ 1
Решили проблему добавлением задачи в Task Scheduler, которая периодически проверяет состояние сервиса и стартует его, если он еще не запущен. Наверное, можно было бы обойтись net start servicename, но прикрутили чуть более длинный скрипт: @ECHO OFF SET ServiceName=%~1 SC QUERYEX "%ServiceName%" | FIND "STATE" | FIND /v "RUNNING" > NUL && ( ECHO %ServiceName% is not running ECHO START %ServiceName% NET START "%ServiceName%" > NUL || ( ECHO "%ServiceName%" wont start EXIT /B 1 ) ECHO "%ServiceName%" is started EXIT /B 0 ) || ( ECHO "%ServiceName%" is running EXIT /B 0 ) Скрипт для создания таска через Octopus: $ErrorActionPreference = "Stop"; Set-StrictMode -Version "Latest"; # use http://msdn.microsoft.com/en-us/library/windows/desktop/bb736357(v=vs.85).aspx for API reference Function Create-ScheduledTask($TaskName,$RunAsUser,$RunAsPassword,$TaskRun,$Arguments,$Schedule,$StartTime,$StartDate,$RunWithElevatedPermissions,$Days,$Interval,$Duration) { # SCHTASKS /Create [/S system [/U username [/P [password]]]] # [/RU username [/RP password]] /SC schedule [/MO modifier] [/D day] # [/M months] [/I idletime] /TN taskname /TR taskrun [/ST starttime] # [/RI interval] [ {/ET endtime | /DU duration} [/K] [/XML xmlfile] [/V1]] # [/SD startdate] [/ED enddate] [/IT | /NP] [/Z] [/F] [/HRESULT] [/?] # note - /RL and /DELAY appear in the "Parameter list" for "SCHTASKS /Create /?" but not in the syntax above $argumentList = @(); $argumentList += @( "/Create" ); $argumentList += @( "/RU", $RunAsUser ); if( -not (StringIsNullOrWhiteSpace($RunAsPassword))) { $argumentList += @( "/RP", $RunAsPassword ); } $argumentList += @( "/SC", $Schedule ); if( -not (StringIsNullOrWhiteSpace($Interval)) ) { switch -Regex ($Schedule) { "MINUTE|HOURLY|ONLOGON|ONIDLE" { $argumentList += @( "/MO", $Interval ); } "WEEKLY|MONTHLY" { $argumentList += @( "/RI", $Interval ); } "ONCE|ONSTART|ONEVENT" { # we don't currently support providing an XPATH query string throw new-object System.NotImplementedException("Unsupported schedule option '$Schedule'."); } } } if( -not (StringIsNullOrWhiteSpace($Days)) -And $Schedule -Ne "DAILY" ) { if($Schedule -ne "WEEKDAYS") { $argumentList += @( "/D", $Days ); } else { $argumentList += @( "/D", "MON,TUE,WED,THU,FRI" ); } } $argumentList += @( "/TN", "`"$TaskName`"" ); if( $Arguments ) { $argumentList += @( "/TR", "`"'$TaskRun' $Arguments`"" ); } else { $argumentList += @( "/TR", "`"'$TaskRun'`"" ); } if( -not (StringIsNullOrWhiteSpace($StartTime)) ) { $argumentList += @( "/ST", $StartTime ); } if( -not (StringIsNullOrWhiteSpace($Duration)) ) { $argumentList += @( "/DU", $Duration ); } if( -not (StringIsNullOrWhiteSpace($StartDate)) ) { $argumentList += @( "/SD", $StartDate ); } $argumentList += @( "/F" ); if( $RunWithElevatedPermissions ) { $argumentList += @( "/RL", "HIGHEST" ); } Invoke-CommandLine -FilePath "$($env:SystemRoot)\System32\schtasks.exe" ` -ArgumentList $argumentList; } Function Delete-ScheduledTask($TaskName) { # SCHTASKS /Delete [/S system [/U username [/P [password]]]] # /TN taskname [/F] [/HRESULT] [/?] Invoke-CommandLine -FilePath "$($env:SystemRoot)\System32\schtasks.exe" ` -ArgumentList @( "/Delete", "/S", "localhost", "/TN", "`"$TaskName`"", "/F" ); } Function Stop-ScheduledTask($TaskName) { # SCHTASKS /End [/S system [/U username [/P [password]]]] # /TN taskname [/HRESULT] [/?] Invoke-CommandLine -FilePath "$($env:SystemRoot)\System32\schtasks.exe" ` -ArgumentList @( "/End", "/S", "localhost", "/TN", "`"$TaskName`"" ); } Function Start-ScheduledTask($TaskName) { # SCHTASKS /Run [/S system [/U username [/P [password]]]] [/I] # /TN taskname [/HRESULT] [/?] Invoke-CommandLine -FilePath "$($env:SystemRoot)\System32\schtasks.exe" ` -ArgumentList @( "/Run", "/S", "localhost", "/TN", "`"$TaskName`"" ); } Function Enable-ScheduledTask($TaskName) { # SCHTASKS /Change [/S system [/U username [/P [password]]]] /TN taskname # { [/RU runasuser] [/RP runaspassword] [/TR taskrun] [/ST starttime] # [/RI interval] [ {/ET endtime | /DU duration} [/K] ] # [/SD startdate] [/ED enddate] [/ENABLE | /DISABLE] [/IT] [/Z] } # [/HRESULT] [/?] Invoke-CommandLine -FilePath "$($env:SystemRoot)\System32\schtasks.exe" ` -ArgumentList @( "/Change", "/S", "localhost", "/TN", "`"$TaskName`"", "/ENABLE" ); } Function ScheduledTask-Exists($taskName) { $schedule = new-object -com Schedule.Service $schedule.connect() $tasks = $schedule.getfolder("\").gettasks(0) foreach ($task in ($tasks | select Name)) { #echo "TASK: $($task.name)" if($task.Name -eq $taskName) { #write-output "$task already exists" return $true } } return $false } Function StringIsNullOrWhitespace([string] $string) { if ($string -ne $null) { $string = $string.Trim() } return [string]::IsNullOrEmpty($string) } function Invoke-CommandLine { param ( [Parameter(Mandatory=$true)] [string] $FilePath, [Parameter(Mandatory=$false)] [string[]] $ArgumentList = @( ), [Parameter(Mandatory=$false)] [string[]] $SuccessCodes = @( 0 ) ) write-host ($FilePath + " " + ($ArgumentList -join " ")); $process = Start-Process -FilePath $FilePath -ArgumentList $ArgumentList -Wait -NoNewWindow -PassThru; if( $SuccessCodes -notcontains $process.ExitCode ) { throw new-object System.InvalidOperationException("process terminated with exit code '$($process.ExitCode)'."); } } function Invoke-OctopusStep { param ( [Parameter(Mandatory=$true)] [hashtable] $OctopusParameters ) $taskName = $OctopusParameters['TaskName'] $runAsUser = $OctopusParameters['RunAsUser'] $runAsPassword = $OctopusParameters['RunAsPassword'] $command = $OctopusParameters['Command'] $arguments = $OctopusParameters['Arguments'] $schedule = $OctopusParameters['Schedule'] $startTime = $OctopusParameters['StartTime'] $startDate = $OctopusParameters['StartDate'] if( $OctopusParameters.ContainsKey("RunWithElevatedPermissions") ) { $runWithElevatedPermissions = [boolean]::Parse($OctopusParameters['RunWithElevatedPermissions']) } else { $runWithElevatedPermissions = $false; } $days = $OctopusParameters['Days'] $interval = $OctopusParameters['Interval'] $duration = $OctopusParameters['Duration'] if((ScheduledTask-Exists($taskName))){ Write-Output "$taskName already exists, Tearing down..." Write-Output "Stopping $taskName..." Stop-ScheduledTask($taskName) Write-Output "Successfully Stopped $taskName" Write-Output "Deleting $taskName..." Delete-ScheduledTask($taskName) Write-Output "Successfully Deleted $taskName" } Write-Output "Creating Scheduled Task - $taskName" Create-ScheduledTask $taskName $runAsUser $runAsPassword $command $arguments $schedule $startTime $startDate $runWithElevatedPermissions $days $interval $duration Write-Output "Successfully Created $taskName" Enable-ScheduledTask($taskName) Write-Output "$taskName enabled" } # only execute the step if it's called from octopus deploy, # and skip it if we're runnning inside a Pester test if( Test-Path -Path "Variable:OctopusParameters" ) { $ParamsForRunning = @{ TaskName = 'Service WatchDog' RunAsUser= 'System' Command = $OctopusParameters["Octopus.Action[Deploy Worker].Output.Package.InstallationDirectoryPath"]+"\check-service.cmd" Arguments = 'SomeWorkerServiceName' Schedule = 'MINUTE' Interval = 20 } Invoke-OctopusStep -OctopusParameters $ParamsForRunning; } Не через Octopus - специфические вещи можно заменить на прямой вызов. Из неудобств - при умышленной остановке сервиса приходится менять ему Startup Type на Disabled, чтобы избежать автозапуска.
Комментариев нет:
Отправить комментарий