Skip to content

Commit 11ecc62

Browse files
authored
Handle IIS OnCompleted callbacks later #17268 (#17756)
1 parent efd765e commit 11ecc62

File tree

4 files changed

+75
-60
lines changed

4 files changed

+75
-60
lines changed

src/Servers/IIS/IIS/src/Core/IISHttpContext.cs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ public unsafe void SetResponseHeaders()
409409
}
410410
}
411411

412-
public abstract Task<bool> ProcessRequestAsync();
412+
public abstract Task ProcessRequestAsync();
413413

414414
public void OnStarting(Func<object, Task> callback, object state)
415415
{
@@ -599,30 +599,19 @@ public void Execute()
599599

600600
private async Task HandleRequest()
601601
{
602-
bool successfulRequest = false;
603602
try
604603
{
605-
successfulRequest = await ProcessRequestAsync();
604+
await ProcessRequestAsync();
606605
}
607606
catch (Exception ex)
608607
{
609608
_logger.LogError(0, ex, $"Unexpected exception in {nameof(IISHttpContext)}.{nameof(HandleRequest)}.");
610609
}
611610
finally
612611
{
613-
// Post completion after completing the request to resume the state machine
614-
PostCompletion(ConvertRequestCompletionResults(successfulRequest));
615-
616-
617612
// Dispose the context
618613
Dispose();
619614
}
620615
}
621-
622-
private static NativeMethods.REQUEST_NOTIFICATION_STATUS ConvertRequestCompletionResults(bool success)
623-
{
624-
return success ? NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_CONTINUE
625-
: NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_FINISH_REQUEST;
626-
}
627616
}
628617
}

src/Servers/IIS/IIS/src/Core/IISHttpContextOfT.cs

Lines changed: 59 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -21,32 +21,33 @@ public IISHttpContextOfT(MemoryPool<byte> memoryPool, IHttpApplication<TContext>
2121
_application = application;
2222
}
2323

24-
public override async Task<bool> ProcessRequestAsync()
24+
public override async Task ProcessRequestAsync()
2525
{
26-
InitializeContext();
27-
2826
var context = default(TContext);
2927
var success = true;
3028

3129
try
3230
{
33-
context = _application.CreateContext(this);
31+
InitializeContext();
32+
33+
try
34+
{
35+
context = _application.CreateContext(this);
36+
37+
await _application.ProcessRequestAsync(context);
38+
}
39+
catch (BadHttpRequestException ex)
40+
{
41+
SetBadRequestState(ex);
42+
ReportApplicationError(ex);
43+
success = false;
44+
}
45+
catch (Exception ex)
46+
{
47+
ReportApplicationError(ex);
48+
success = false;
49+
}
3450

35-
await _application.ProcessRequestAsync(context);
36-
}
37-
catch (BadHttpRequestException ex)
38-
{
39-
SetBadRequestState(ex);
40-
ReportApplicationError(ex);
41-
success = false;
42-
}
43-
catch (Exception ex)
44-
{
45-
ReportApplicationError(ex);
46-
success = false;
47-
}
48-
finally
49-
{
5051
await CompleteResponseBodyAsync();
5152
_streams.Stop();
5253

@@ -56,36 +57,18 @@ public override async Task<bool> ProcessRequestAsync()
5657
// Dispose
5758
}
5859

59-
if (_onCompleted != null)
60+
if (!_requestAborted)
6061
{
61-
await FireOnCompleted();
62+
await ProduceEnd();
63+
}
64+
else if (!HasResponseStarted && _requestRejectedException == null)
65+
{
66+
// If the request was aborted and no response was sent, there's no
67+
// meaningful status code to log.
68+
StatusCode = 0;
69+
success = false;
6270
}
63-
}
64-
65-
if (!_requestAborted)
66-
{
67-
await ProduceEnd();
68-
}
69-
else if (!HasResponseStarted && _requestRejectedException == null)
70-
{
71-
// If the request was aborted and no response was sent, there's no
72-
// meaningful status code to log.
73-
StatusCode = 0;
74-
success = false;
75-
}
7671

77-
try
78-
{
79-
_application.DisposeContext(context, _applicationException);
80-
}
81-
catch (Exception ex)
82-
{
83-
// TODO Log this
84-
_applicationException = _applicationException ?? ex;
85-
success = false;
86-
}
87-
finally
88-
{
8972
// Complete response writer and request reader pipe sides
9073
_bodyOutput.Dispose();
9174
_bodyInputPipe?.Reader.Complete();
@@ -104,7 +87,36 @@ public override async Task<bool> ProcessRequestAsync()
10487
await _readBodyTask;
10588
}
10689
}
107-
return success;
90+
catch (Exception ex)
91+
{
92+
success = false;
93+
ReportApplicationError(ex);
94+
}
95+
finally
96+
{
97+
// We're done with anything that touches the request or response, unblock the client.
98+
PostCompletion(ConvertRequestCompletionResults(success));
99+
100+
if (_onCompleted != null)
101+
{
102+
await FireOnCompleted();
103+
}
104+
105+
try
106+
{
107+
_application.DisposeContext(context, _applicationException);
108+
}
109+
catch (Exception ex)
110+
{
111+
ReportApplicationError(ex);
112+
}
113+
}
114+
}
115+
116+
private static NativeMethods.REQUEST_NOTIFICATION_STATUS ConvertRequestCompletionResults(bool success)
117+
{
118+
return success ? NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_CONTINUE
119+
: NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_FINISH_REQUEST;
108120
}
109121
}
110122
}

src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/ResponseBodyTests.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,12 @@ public async Task ResponseBodyTest_FlushedPipeAndThenUnflushedPipe_AutoFlushed()
3030
{
3131
Assert.Equal(20, (await _fixture.Client.GetByteArrayAsync($"/FlushedPipeAndThenUnflushedPipe")).Length);
3232
}
33+
34+
[ConditionalFact]
35+
[RequiresNewHandler]
36+
public async Task ResponseBodyTest_BodyCompletionNotBlockedByOnCompleted()
37+
{
38+
Assert.Equal("SlowOnCompleted", await _fixture.Client.GetStringAsync($"/SlowOnCompleted"));
39+
}
3340
}
3441
}

src/Servers/IIS/IIS/test/testassets/InProcessWebSite/Startup.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,5 +1016,12 @@ public async Task HTTPS_PORT(HttpContext context)
10161016

10171017
await context.Response.WriteAsync(httpsPort.HasValue ? httpsPort.Value.ToString() : "NOVALUE");
10181018
}
1019+
1020+
public async Task SlowOnCompleted(HttpContext context)
1021+
{
1022+
// This shouldn't block the response or the server from shutting down.
1023+
context.Response.OnCompleted(() => Task.Delay(TimeSpan.FromMinutes(5)));
1024+
await context.Response.WriteAsync("SlowOnCompleted");
1025+
}
10191026
}
10201027
}

0 commit comments

Comments
 (0)