Skip to content

Commit 125ab8f

Browse files
alrodgzuber
authored andcommitted
Restart only if needed (Resolves #7089)
1 parent 725b11e commit 125ab8f

File tree

4 files changed

+81
-37
lines changed

4 files changed

+81
-37
lines changed

src/WebJobs.Script.WebHost/Controllers/FunctionsController.cs

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,10 @@ public async Task<IActionResult> CreateOrUpdate(string name, [FromBody] Function
7474

7575
bool success, configChanged;
7676
FunctionMetadataResponse functionMetadataResponse;
77-
using (fileMonitoringService.SuspendRestart())
77+
using (fileMonitoringService.SuspendRestart(true))
7878
{
7979
(success, configChanged, functionMetadataResponse) = await _functionsManager.CreateOrUpdate(name, functionMetadata, Request);
8080
}
81-
await ScheduleRestartAsync(fileMonitoringService);
8281

8382
if (success)
8483
{
@@ -175,11 +174,10 @@ public async Task<IActionResult> Delete(string name, [FromServices] IFileMonitor
175174

176175
bool deleted;
177176
string error;
178-
using (fileMonitoringService.SuspendRestart())
177+
using (fileMonitoringService.SuspendRestart(true))
179178
{
180179
(deleted, error) = await _functionsManager.TryDeleteFunction(function);
181180
}
182-
await ScheduleRestartAsync(fileMonitoringService);
183181

184182
if (deleted)
185183
{
@@ -220,15 +218,5 @@ public IActionResult Download([FromServices] IOptions<ScriptApplicationHostOptio
220218
FileDownloadName = (System.Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME") ?? "functions") + ".zip"
221219
};
222220
}
223-
224-
private async Task ScheduleRestartAsync(IFileMonitoringService fileMonitoringService)
225-
{
226-
Task restartTask = null;
227-
using (System.Threading.ExecutionContext.SuppressFlow())
228-
{
229-
restartTask = fileMonitoringService.ScheduleRestartAsync(false);
230-
}
231-
await restartTask;
232-
}
233221
}
234222
}

src/WebJobs.Script.WebHost/FileMonitoringService.cs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public class FileMonitoringService : IFileMonitoringService, IDisposable
3838
private AutoRecoveringFileSystemWatcher _debugModeFileWatcher;
3939
private AutoRecoveringFileSystemWatcher _diagnosticModeFileWatcher;
4040
private FileWatcherEventSource _fileEventSource;
41+
private bool _restartScheduled;
4142
private bool _shutdownScheduled;
4243
private long _restartRequested;
4344
private bool _disposed = false;
@@ -99,31 +100,43 @@ public Task StopAsync(CancellationToken cancellationToken)
99100
return Task.CompletedTask;
100101
}
101102

102-
public IDisposable SuspendRestart()
103+
public IDisposable SuspendRestart(bool autoRestart)
103104
{
104-
return new SuspendRestartRequest(this);
105+
return new SuspendRestartRequest(this, autoRestart);
105106
}
106107

107-
public void ResumeRestart()
108+
private void ResumeRestartIfScheduled()
108109
{
109-
Interlocked.Decrement(ref _suspensionRequestsCount);
110-
_typedLogger.LogDebug($"Resuming restart. ({_suspensionRequestsCount} requests).");
110+
if (_restartScheduled)
111+
{
112+
using (System.Threading.ExecutionContext.SuppressFlow())
113+
{
114+
_typedLogger.LogDebug("Resuming scheduled restart.");
115+
Task.Run(async () => await ScheduleRestartAsync());
116+
}
117+
}
111118
}
112119

113-
public async Task ScheduleRestartAsync(bool shutdown)
120+
private async Task ScheduleRestartAsync(bool shutdown)
114121
{
122+
_restartScheduled = true;
115123
if (shutdown)
116124
{
117125
_shutdownScheduled = true;
118126
}
119127

128+
await ScheduleRestartAsync();
129+
}
130+
131+
private async Task ScheduleRestartAsync()
132+
{
120133
if (Interlocked.Read(ref _suspensionRequestsCount) > 0)
121134
{
122135
_logger.LogDebug("Restart requested while currently suspended. Ignoring request.");
123136
}
124137
else
125138
{
126-
if (shutdown || _shutdownScheduled)
139+
if (_shutdownScheduled)
127140
{
128141
_shutdown();
129142
}
@@ -396,11 +409,13 @@ await Utility.InvokeWithRetriesAsync(() =>
396409
private class SuspendRestartRequest : IDisposable
397410
{
398411
private FileMonitoringService _fileMonitoringService;
412+
private bool _autoResume;
399413
private bool _disposed = false;
400414

401-
public SuspendRestartRequest(FileMonitoringService fileMonitoringService)
415+
public SuspendRestartRequest(FileMonitoringService fileMonitoringService, bool autoResume)
402416
{
403417
_fileMonitoringService = fileMonitoringService;
418+
_autoResume = autoResume;
404419
Interlocked.Increment(ref _fileMonitoringService._suspensionRequestsCount);
405420
_fileMonitoringService._typedLogger.LogDebug($"Entering restart suspension scope. ({_fileMonitoringService._suspensionRequestsCount} requests).");
406421
}
@@ -411,6 +426,10 @@ public void Dispose()
411426
{
412427
Interlocked.Decrement(ref _fileMonitoringService._suspensionRequestsCount);
413428
_fileMonitoringService._typedLogger.LogDebug($"Exiting restart suspension scope. ({_fileMonitoringService._suspensionRequestsCount} requests).");
429+
if (_autoResume)
430+
{
431+
_fileMonitoringService.ResumeRestartIfScheduled();
432+
}
414433
_disposed = true;
415434
}
416435
}

src/WebJobs.Script.WebHost/IFileMonitoringService.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,6 @@ namespace Microsoft.Azure.WebJobs.Script.WebHost
99
{
1010
public interface IFileMonitoringService : IHostedService
1111
{
12-
IDisposable SuspendRestart();
13-
14-
void ResumeRestart();
15-
16-
Task ScheduleRestartAsync(bool shutdown);
12+
IDisposable SuspendRestart(bool autoResume);
1713
}
1814
}

test/WebJobs.Script.Tests/FileMonitoringServiceTests.cs

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -139,22 +139,20 @@ public static async Task SuspendRestart_Restart()
139139

140140
_ = Task.Run(async () =>
141141
{
142-
using (fileMonitoringService.SuspendRestart())
142+
using (fileMonitoringService.SuspendRestart(true))
143143
{
144144
mockEventManager.Publish(randomFileEvent);
145145
await Task.Delay(1000);
146146
}
147-
await fileMonitoringService.ScheduleRestartAsync(false);
148147
e1.Set();
149148
});
150149

151150
_ = Task.Run(async () =>
152151
{
153-
using (fileMonitoringService.SuspendRestart())
152+
using (fileMonitoringService.SuspendRestart(true))
154153
{
155154
await Task.Delay(2000);
156155
}
157-
await fileMonitoringService.ScheduleRestartAsync(false);
158156
e2.Set();
159157
});
160158

@@ -165,7 +163,52 @@ public static async Task SuspendRestart_Restart()
165163

166164
// wait for restart
167165
await Task.Delay(1000);
168-
mockScriptHostManager.Verify(m => m.RestartHostAsync(default));
166+
mockScriptHostManager.Verify(m => m.RestartHostAsync(default), Times.Once);
167+
await fileMonitoringService.StopAsync(CancellationToken.None);
168+
}
169+
}
170+
171+
[Fact]
172+
public static async Task SuspendRestart_Restart_NestedUsings_Work()
173+
{
174+
using (var directory = new TempDirectory())
175+
{
176+
// Setup
177+
string tempDir = directory.Path;
178+
Directory.CreateDirectory(Path.Combine(tempDir, "Host"));
179+
180+
var jobHostOptions = new ScriptJobHostOptions
181+
{
182+
RootLogPath = tempDir,
183+
RootScriptPath = tempDir,
184+
FileWatchingEnabled = true,
185+
WatchFiles = { "host.json" }
186+
};
187+
var loggerFactory = new LoggerFactory();
188+
var mockApplicationLifetime = new Mock<IApplicationLifetime>();
189+
var mockScriptHostManager = new Mock<IScriptHostManager>();
190+
var mockEventManager = new ScriptEventManager();
191+
var environment = new TestEnvironment();
192+
193+
// Act
194+
FileMonitoringService fileMonitoringService = new FileMonitoringService(new OptionsWrapper<ScriptJobHostOptions>(jobHostOptions),
195+
loggerFactory, mockEventManager, mockApplicationLifetime.Object, mockScriptHostManager.Object, environment);
196+
await fileMonitoringService.StartAsync(new CancellationToken(canceled: false));
197+
198+
var randomFileEventArgs = new FileSystemEventArgs(WatcherChangeTypes.Created, tempDir, "host.json");
199+
FileEvent randomFileEvent = new FileEvent("ScriptFiles", randomFileEventArgs);
200+
201+
using (fileMonitoringService.SuspendRestart(true))
202+
{
203+
using (fileMonitoringService.SuspendRestart(true))
204+
{
205+
}
206+
await Task.Delay(1000);
207+
mockScriptHostManager.Verify(m => m.RestartHostAsync(default), Times.Never);
208+
mockEventManager.Publish(randomFileEvent);
209+
}
210+
await Task.Delay(1000);
211+
mockScriptHostManager.Verify(m => m.RestartHostAsync(default), Times.Once);
169212
await fileMonitoringService.StopAsync(CancellationToken.None);
170213
}
171214
}
@@ -205,22 +248,20 @@ public static async Task SuspendRestart_Shutdown()
205248

206249
_ = Task.Run(async () =>
207250
{
208-
using (fileMonitoringService.SuspendRestart())
251+
using (fileMonitoringService.SuspendRestart(true))
209252
{
210253
mockEventManager.Publish(randomFileEvent);
211254
await Task.Delay(1000);
212255
}
213-
await fileMonitoringService.ScheduleRestartAsync(true);
214256
e1.Set();
215257
});
216258

217259
_ = Task.Run(async () =>
218260
{
219-
using (fileMonitoringService.SuspendRestart())
261+
using (fileMonitoringService.SuspendRestart(true))
220262
{
221263
await Task.Delay(2000);
222264
}
223-
await fileMonitoringService.ScheduleRestartAsync(true);
224265
e2.Set();
225266
});
226267

@@ -231,7 +272,7 @@ public static async Task SuspendRestart_Shutdown()
231272

232273
// wait for restart
233274
await Task.Delay(1000);
234-
mockApplicationLifetime.Verify(m => m.StopApplication());
275+
mockApplicationLifetime.Verify(m => m.StopApplication(), Times.Once);
235276
await fileMonitoringService.StopAsync(CancellationToken.None);
236277
}
237278
}

0 commit comments

Comments
 (0)