From abf4ba88f4327dc23621d7eed990cb18b7779111 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 19 May 2024 17:04:13 +0100 Subject: [PATCH 1/2] Fix potential deadlock Fix a potential deadlock where the server never stops by checking the result of `WaitToReadAsync()` and by passing the `CancellationToken` to `ReadAsync()`. --- src/AwsLambdaTestServer/RuntimeHandler.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/AwsLambdaTestServer/RuntimeHandler.cs b/src/AwsLambdaTestServer/RuntimeHandler.cs index 879f0577..20fb1d96 100644 --- a/src/AwsLambdaTestServer/RuntimeHandler.cs +++ b/src/AwsLambdaTestServer/RuntimeHandler.cs @@ -134,8 +134,12 @@ internal async Task HandleNextAsync(HttpContext httpContext) using var cts = CancellationTokenSource.CreateLinkedTokenSource(httpContext.RequestAborted, _cancellationToken); // Wait until there is a request to process - await _requests.Reader.WaitToReadAsync(cts.Token).ConfigureAwait(false); - request = await _requests.Reader.ReadAsync().ConfigureAwait(false); + if (!await _requests.Reader.WaitToReadAsync(cts.Token).ConfigureAwait(false)) + { + cts.Token.ThrowIfCancellationRequested(); + } + + request = await _requests.Reader.ReadAsync(cts.Token).ConfigureAwait(false); } catch (Exception ex) when (ex is OperationCanceledException or ChannelClosedException) { From 46475135d2568daa2eba71c8c9eef0d9722d7d03 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 19 May 2024 17:22:38 +0100 Subject: [PATCH 2/2] Allow server longer to start Give the server as long as it needs to start, then cancel after started. --- .../AwsLambdaTestServer.Tests/AwsIntegrationTests.cs | 4 +++- tests/AwsLambdaTestServer.Tests/Examples.cs | 5 ++++- .../HttpLambdaTestServerTests.cs | 12 +++++++++--- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/tests/AwsLambdaTestServer.Tests/AwsIntegrationTests.cs b/tests/AwsLambdaTestServer.Tests/AwsIntegrationTests.cs index 01fda86d..91d8c95c 100644 --- a/tests/AwsLambdaTestServer.Tests/AwsIntegrationTests.cs +++ b/tests/AwsLambdaTestServer.Tests/AwsIntegrationTests.cs @@ -19,10 +19,12 @@ public static async Task Runtime_Generates_Valid_Aws_Trace_Id() Xunit.Skip.If(GetAwsCredentials() is null, "No AWS credentials are configured."); using var server = new LambdaTestServer(); - using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + using var cancellationTokenSource = new CancellationTokenSource(); await server.StartAsync(cancellationTokenSource.Token); + cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(5)); + var request = new QueueExistsRequest() { QueueName = Guid.NewGuid().ToString(), diff --git a/tests/AwsLambdaTestServer.Tests/Examples.cs b/tests/AwsLambdaTestServer.Tests/Examples.cs index 29e7b86a..0a4f40b1 100644 --- a/tests/AwsLambdaTestServer.Tests/Examples.cs +++ b/tests/AwsLambdaTestServer.Tests/Examples.cs @@ -18,11 +18,14 @@ public static async Task Function_Can_Process_Request() // Create a cancellation token that stops the server listening for new requests. // Auto-cancel the server after 2 seconds in case something goes wrong and the request is not handled. - using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(2)); + using var cancellationTokenSource = new CancellationTokenSource(); // Start the test server so it is ready to listen for requests from the Lambda runtime await server.StartAsync(cancellationTokenSource.Token); + // Now that the server has started, cancel it after 2 seconds if no requests are processed + cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(2)); + // Create a test request for the Lambda function being tested var value = new MyRequest() { diff --git a/tests/AwsLambdaTestServer.Tests/HttpLambdaTestServerTests.cs b/tests/AwsLambdaTestServer.Tests/HttpLambdaTestServerTests.cs index 774cbdd2..72c46611 100644 --- a/tests/AwsLambdaTestServer.Tests/HttpLambdaTestServerTests.cs +++ b/tests/AwsLambdaTestServer.Tests/HttpLambdaTestServerTests.cs @@ -23,10 +23,12 @@ void Configure(IServiceCollection services) => services.AddLogging((builder) => builder.AddXUnit(this)); using var server = new HttpLambdaTestServer(Configure); - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2)); + using var cts = new CancellationTokenSource(); await server.StartAsync(cts.Token); + cts.CancelAfter(TimeSpan.FromSeconds(2)); + var context = await server.EnqueueAsync(@"{""Values"": [ 1, 2, 3 ]}"); _ = Task.Run(async () => @@ -62,10 +64,12 @@ void Configure(IServiceCollection services) => services.AddLogging((builder) => builder.AddXUnit(this)); using var server = new LambdaTestServer(Configure); - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2)); + using var cts = new CancellationTokenSource(); await server.StartAsync(cts.Token); + cts.CancelAfter(TimeSpan.FromSeconds(2)); + var context = await server.EnqueueAsync(@"{""Values"": null}"); _ = Task.Run(async () => @@ -99,10 +103,12 @@ void Configure(IServiceCollection services) => services.AddLogging((builder) => builder.AddXUnit(this)); using var server = new LambdaTestServer(Configure); - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2)); + using var cts = new CancellationTokenSource(); await server.StartAsync(cts.Token); + cts.CancelAfter(TimeSpan.FromSeconds(2)); + var channels = new List<(int Expected, LambdaTestContext Context)>(); for (int i = 0; i < 10; i++)