|
3 | 3 |
|
4 | 4 | using System;
|
5 | 5 | using System.Collections.Generic;
|
| 6 | +using System.Diagnostics; |
6 | 7 | using System.IO;
|
7 | 8 | using System.Linq;
|
8 | 9 | using System.Net.Http;
|
|
13 | 14 | using Microsoft.AspNetCore.Builder;
|
14 | 15 | using Microsoft.AspNetCore.Hosting;
|
15 | 16 | using Microsoft.AspNetCore.TestHost;
|
| 17 | +using Microsoft.Azure.WebJobs.Host.Executors; |
16 | 18 | using Microsoft.Azure.WebJobs.Script.ExtensionBundle;
|
17 | 19 | using Microsoft.Azure.WebJobs.Script.Models;
|
18 | 20 | using Microsoft.Azure.WebJobs.Script.WebHost;
|
@@ -43,6 +45,13 @@ public class TestFunctionHost : IDisposable
|
43 | 45 | private readonly TestLoggerProvider _scriptHostLoggerProvider = new TestLoggerProvider();
|
44 | 46 | private readonly WebJobsScriptHostService _hostService;
|
45 | 47 |
|
| 48 | + private readonly Timer _stillRunningTimer; |
| 49 | + private readonly DateTimeOffset _created = DateTimeOffset.UtcNow; |
| 50 | + private readonly string _createdStack; |
| 51 | + private readonly string _id = Guid.NewGuid().ToString(); |
| 52 | + private bool _timerFired = false; |
| 53 | + private bool _isDisposed = false; |
| 54 | + |
46 | 55 | public TestFunctionHost(string scriptPath,
|
47 | 56 | Action<IServiceCollection> configureWebHostServices = null,
|
48 | 57 | Action<IWebJobsBuilder> configureScriptHostWebJobsBuilder = null,
|
@@ -139,6 +148,12 @@ public TestFunctionHost(string scriptPath, string logPath,
|
139 | 148 | lifetime.ApplicationStopping.Register(async () => await _testServer.Host.StopAsync());
|
140 | 149 |
|
141 | 150 | StartAsync().GetAwaiter().GetResult();
|
| 151 | + |
| 152 | + _stillRunningTimer = new Timer(StillRunningCallback, _testServer, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30)); |
| 153 | + |
| 154 | + // store off a bit of the creation stack for easier debugging if this host doesn't shut down. |
| 155 | + var stack = new StackTrace(true).ToString().Split(Environment.NewLine).Take(5); |
| 156 | + _createdStack = string.Join($"{Environment.NewLine} ", stack); |
142 | 157 | }
|
143 | 158 |
|
144 | 159 | public IServiceProvider JobHostServices => _hostService.Services;
|
@@ -172,6 +187,39 @@ public async Task RestartAsync(CancellationToken cancellationToken)
|
172 | 187 | await _hostService.RestartHostAsync(cancellationToken);
|
173 | 188 | }
|
174 | 189 |
|
| 190 | + private void StillRunningCallback(object state) |
| 191 | + { |
| 192 | + var idProvider = JobHostServices?.GetService<IHostIdProvider>(); |
| 193 | + var jobOptions = JobHostServices?.GetService<IOptions<ScriptJobHostOptions>>(); |
| 194 | + |
| 195 | + if (idProvider == null || jobOptions == null) |
| 196 | + { |
| 197 | + return; |
| 198 | + } |
| 199 | + |
| 200 | + var functions = jobOptions?.Value.Functions ?? new[] { "" }; |
| 201 | + |
| 202 | + string allowList = $"[{string.Join(", ", functions)}]"; |
| 203 | + string hostId = idProvider.GetHostIdAsync(CancellationToken.None).Result; |
| 204 | + |
| 205 | + var ago = (int)(DateTime.UtcNow - _created).TotalSeconds; |
| 206 | + |
| 207 | + // This helps debugging tests that may not be disposing their hosts. |
| 208 | + StringBuilder sb = new StringBuilder(); |
| 209 | + |
| 210 | + sb.AppendLine($"A host created at '{_created:yyyy-MM-dd HH:mm:ss}' ({ago}s ago) is still running. Details:"); |
| 211 | + sb.AppendLine($" Host id: {hostId}"); |
| 212 | + sb.AppendLine($" Test host id: {_id}"); |
| 213 | + sb.AppendLine($" ScriptRoot: {jobOptions.Value.RootScriptPath}"); |
| 214 | + sb.AppendLine($" Allow list: {allowList}"); |
| 215 | + sb.AppendLine($" The captured stack from this test host's constructor:"); |
| 216 | + sb.AppendLine(_createdStack); |
| 217 | + |
| 218 | + Console.Write(sb.ToString()); |
| 219 | + |
| 220 | + _timerFired = true; |
| 221 | + } |
| 222 | + |
175 | 223 | private Task StartAsync()
|
176 | 224 | {
|
177 | 225 | bool exit = false;
|
@@ -286,8 +334,21 @@ public async Task<HostStatus> GetHostStatusAsync()
|
286 | 334 |
|
287 | 335 | public void Dispose()
|
288 | 336 | {
|
289 |
| - HttpClient.Dispose(); |
290 |
| - _testServer.Dispose(); |
| 337 | + if (!_isDisposed) |
| 338 | + { |
| 339 | + HttpClient.Dispose(); |
| 340 | + _testServer.Dispose(); |
| 341 | + |
| 342 | + _stillRunningTimer?.Change(-1, -1); |
| 343 | + _stillRunningTimer?.Dispose(); |
| 344 | + |
| 345 | + if (_timerFired) |
| 346 | + { |
| 347 | + Console.WriteLine($"The test host with id {_id} is now disposed."); |
| 348 | + } |
| 349 | + |
| 350 | + _isDisposed = true; |
| 351 | + } |
291 | 352 | }
|
292 | 353 |
|
293 | 354 | private async Task<bool> IsHostStarted()
|
|
0 commit comments