|
2 | 2 | // Licensed under the MIT License. See License.txt in the project root for license information. |
3 | 3 |
|
4 | 4 | using System; |
| 5 | +using System.Collections.Concurrent; |
5 | 6 | using System.Collections.Generic; |
6 | 7 | using System.Linq; |
7 | 8 | using System.Threading; |
@@ -411,33 +412,48 @@ public async Task ShutdownChannelsIfExist_Succeeds() |
411 | 412 | [Fact] |
412 | 413 | public void ShutdownChannelsIfExist_Race_Succeeds() |
413 | 414 | { |
414 | | - var channel = CreateTestChannel(RpcWorkerConstants.JavaLanguageWorkerName); |
415 | | - string id = channel.Id; |
| 415 | + // This test covers an issue that was fixed by https://github.com/Azure/azure-functions-host/pull/9738 |
| 416 | + // To repro, it requires ShutdownChannelIfExistsAsync to be called by multiple threads simultaneously. Using |
| 417 | + // Tasks did not repro, so using Semaphores and Threads for more precise timing. We run this several times |
| 418 | + // just to ensure it's not continuing to repro (it did not repro on every invocation). |
416 | 419 |
|
417 | | - List<Task<bool>> tasks = new(); |
418 | | - List<Thread> threads = new(); |
419 | | - for (int i = 0; i < 2; i++) |
| 420 | + IEnumerable<bool> RunRaceTest() |
420 | 421 | { |
421 | | - Thread t = new(static (state) => |
| 422 | + var channel = CreateTestChannel(RpcWorkerConstants.JavaLanguageWorkerName); |
| 423 | + |
| 424 | + int count = 4; |
| 425 | + SemaphoreSlim semaphore = new(0, count); |
| 426 | + List<Thread> threads = new(); |
| 427 | + ConcurrentBag<bool> results = new(); |
| 428 | + |
| 429 | + for (int i = 0; i < count; i++) |
422 | 430 | { |
423 | | - var (channelManager, tasks, id) = ((WebHostRpcWorkerChannelManager, List<Task<bool>>, string))state; |
424 | | - tasks.Add(channelManager.ShutdownChannelIfExistsAsync(RpcWorkerConstants.JavaLanguageWorkerName, id)); |
425 | | - }); |
426 | | - threads.Add(t); |
427 | | - } |
| 431 | + Thread t = new(() => |
| 432 | + { |
| 433 | + // Pause threads here. They will all be released simultaneously. |
| 434 | + semaphore.Wait(); |
| 435 | + results.Add(_rpcWorkerChannelManager.ShutdownChannelIfExistsAsync(RpcWorkerConstants.JavaLanguageWorkerName, channel.Id).GetAwaiter().GetResult()); |
| 436 | + }); |
| 437 | + t.Start(); |
| 438 | + threads.Add(t); |
| 439 | + } |
428 | 440 |
|
429 | | - foreach (Thread t in threads) |
430 | | - { |
431 | | - t.Start((_rpcWorkerChannelManager, tasks, id)); |
| 441 | + // Release all threads. |
| 442 | + semaphore.Release(count); |
| 443 | + |
| 444 | + foreach (Thread t in threads) |
| 445 | + { |
| 446 | + t.Join(); |
| 447 | + } |
| 448 | + |
| 449 | + return results; |
432 | 450 | } |
433 | 451 |
|
434 | | - foreach (Thread t in threads) |
| 452 | + for (int i = 0; i < 50; i++) |
435 | 453 | { |
436 | | - t.Join(); |
| 454 | + var results = RunRaceTest(); |
| 455 | + Assert.Single(results, true); |
437 | 456 | } |
438 | | - |
439 | | - // only one should successfully shut down |
440 | | - Assert.Single(tasks, t => t.Result == true); |
441 | 457 | } |
442 | 458 |
|
443 | 459 | [Fact] |
|
0 commit comments