@@ -32,6 +32,8 @@ public class WebJobsScriptHostService : IHostedService, IScriptHostManager, IDis
32
32
private readonly SlidingWindow < bool > _healthCheckWindow ;
33
33
private readonly Timer _hostHealthCheckTimer ;
34
34
private readonly SemaphoreSlim _hostStartSemaphore = new SemaphoreSlim ( 1 , 1 ) ;
35
+ private readonly TaskCompletionSource < bool > _hostStartedSource = new TaskCompletionSource < bool > ( ) ;
36
+ private readonly Task _hostStarted ;
35
37
36
38
private IHost _host ;
37
39
private CancellationTokenSource _startupLoopTokenSource ;
@@ -61,6 +63,8 @@ public WebJobsScriptHostService(IOptionsMonitor<ScriptApplicationHostOptions> ap
61
63
_healthMonitorOptions = healthMonitorOptions ?? throw new ArgumentNullException ( nameof ( healthMonitorOptions ) ) ;
62
64
_logger = loggerFactory . CreateLogger ( ScriptConstants . LogCategoryHostGeneral ) ;
63
65
66
+ _hostStarted = _hostStartedSource . Task ;
67
+
64
68
State = ScriptHostState . Default ;
65
69
66
70
if ( ShouldMonitorHostHealth )
@@ -140,6 +144,12 @@ private async Task StartHostAsync(CancellationToken cancellationToken, int attem
140
144
try
141
145
{
142
146
await _hostStartSemaphore . WaitAsync ( ) ;
147
+
148
+ // Now that we're inside the semaphore, set this task as completed. This prevents
149
+ // restarts from being invoked (via the PlaceholderSpecializationMiddleware) before
150
+ // the IHostedService has ever started.
151
+ _hostStartedSource . TrySetResult ( true ) ;
152
+
143
153
await UnsynchronizedStartHostAsync ( cancellationToken , attemptCount , startupMode ) ;
144
154
}
145
155
finally
@@ -155,11 +165,12 @@ private async Task StartHostAsync(CancellationToken cancellationToken, int attem
155
165
/// </summary>
156
166
private async Task UnsynchronizedStartHostAsync ( CancellationToken cancellationToken , int attemptCount = 0 , JobHostStartupMode startupMode = JobHostStartupMode . Normal )
157
167
{
158
- cancellationToken . ThrowIfCancellationRequested ( ) ;
159
168
IHost localHost = null ;
160
169
161
170
try
162
171
{
172
+ cancellationToken . ThrowIfCancellationRequested ( ) ;
173
+
163
174
// if we were in an error state retain that,
164
175
// otherwise move to default
165
176
if ( State != ScriptHostState . Error )
@@ -332,6 +343,14 @@ public async Task StopAsync(CancellationToken cancellationToken)
332
343
333
344
public async Task RestartHostAsync ( CancellationToken cancellationToken )
334
345
{
346
+ // Do not invoke a restart if the host has not yet been started. This can lead
347
+ // to invalid state.
348
+ if ( ! _hostStarted . IsCompleted )
349
+ {
350
+ _logger . RestartBeforeStart ( ) ;
351
+ await _hostStarted ;
352
+ }
353
+
335
354
_logger . EnteringRestart ( ) ;
336
355
337
356
// If anything is mid-startup, cancel it.
0 commit comments