Skip to content

Commit 052889f

Browse files
Tratcherjkotalik
authored andcommitted
Handle IIS OnCompleted callbacks later #17268 (#17756)
1 parent 45bb9fa commit 052889f

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
@@ -460,7 +460,7 @@ public unsafe void SetResponseTrailers()
460460
}
461461
}
462462

463-
public abstract Task<bool> ProcessRequestAsync();
463+
public abstract Task ProcessRequestAsync();
464464

465465
public void OnStarting(Func<object, Task> callback, object state)
466466
{
@@ -650,30 +650,19 @@ public void Execute()
650650

651651
private async Task HandleRequest()
652652
{
653-
bool successfulRequest = false;
654653
try
655654
{
656-
successfulRequest = await ProcessRequestAsync();
655+
await ProcessRequestAsync();
657656
}
658657
catch (Exception ex)
659658
{
660659
_logger.LogError(0, ex, $"Unexpected exception in {nameof(IISHttpContext)}.{nameof(HandleRequest)}.");
661660
}
662661
finally
663662
{
664-
// Post completion after completing the request to resume the state machine
665-
PostCompletion(ConvertRequestCompletionResults(successfulRequest));
666-
667-
668663
// Dispose the context
669664
Dispose();
670665
}
671666
}
672-
673-
private static NativeMethods.REQUEST_NOTIFICATION_STATUS ConvertRequestCompletionResults(bool success)
674-
{
675-
return success ? NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_CONTINUE
676-
: NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_FINISH_REQUEST;
677-
}
678667
}
679668
}

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

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

27-
public override async Task<bool> ProcessRequestAsync()
27+
public override async Task ProcessRequestAsync()
2828
{
29-
InitializeContext();
30-
3129
var context = default(TContext);
3230
var success = true;
3331

3432
try
3533
{
36-
context = _application.CreateContext(this);
34+
InitializeContext();
35+
36+
try
37+
{
38+
context = _application.CreateContext(this);
39+
40+
await _application.ProcessRequestAsync(context);
41+
}
42+
catch (BadHttpRequestException ex)
43+
{
44+
SetBadRequestState(ex);
45+
ReportApplicationError(ex);
46+
success = false;
47+
}
48+
catch (Exception ex)
49+
{
50+
ReportApplicationError(ex);
51+
success = false;
52+
}
3753

38-
await _application.ProcessRequestAsync(context);
39-
}
40-
catch (BadHttpRequestException ex)
41-
{
42-
SetBadRequestState(ex);
43-
ReportApplicationError(ex);
44-
success = false;
45-
}
46-
catch (Exception ex)
47-
{
48-
ReportApplicationError(ex);
49-
success = false;
50-
}
51-
finally
52-
{
5354
await CompleteResponseBodyAsync();
5455
_streams.Stop();
5556

@@ -66,36 +67,18 @@ public override async Task<bool> ProcessRequestAsync()
6667
SetResetCode(2);
6768
}
6869

69-
if (_onCompleted != null)
70+
if (!_requestAborted)
7071
{
71-
await FireOnCompleted();
72+
await ProduceEnd();
73+
}
74+
else if (!HasResponseStarted && _requestRejectedException == null)
75+
{
76+
// If the request was aborted and no response was sent, there's no
77+
// meaningful status code to log.
78+
StatusCode = 0;
79+
success = false;
7280
}
73-
}
74-
75-
if (!_requestAborted)
76-
{
77-
await ProduceEnd();
78-
}
79-
else if (!HasResponseStarted && _requestRejectedException == null)
80-
{
81-
// If the request was aborted and no response was sent, there's no
82-
// meaningful status code to log.
83-
StatusCode = 0;
84-
success = false;
85-
}
8681

87-
try
88-
{
89-
_application.DisposeContext(context, _applicationException);
90-
}
91-
catch (Exception ex)
92-
{
93-
// TODO Log this
94-
_applicationException = _applicationException ?? ex;
95-
success = false;
96-
}
97-
finally
98-
{
9982
// Complete response writer and request reader pipe sides
10083
_bodyOutput.Dispose();
10184
_bodyInputPipe?.Reader.Complete();
@@ -114,7 +97,36 @@ public override async Task<bool> ProcessRequestAsync()
11497
await _readBodyTask;
11598
}
11699
}
117-
return success;
100+
catch (Exception ex)
101+
{
102+
success = false;
103+
ReportApplicationError(ex);
104+
}
105+
finally
106+
{
107+
// We're done with anything that touches the request or response, unblock the client.
108+
PostCompletion(ConvertRequestCompletionResults(success));
109+
110+
if (_onCompleted != null)
111+
{
112+
await FireOnCompleted();
113+
}
114+
115+
try
116+
{
117+
_application.DisposeContext(context, _applicationException);
118+
}
119+
catch (Exception ex)
120+
{
121+
ReportApplicationError(ex);
122+
}
123+
}
124+
}
125+
126+
private static NativeMethods.REQUEST_NOTIFICATION_STATUS ConvertRequestCompletionResults(bool success)
127+
{
128+
return success ? NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_CONTINUE
129+
: NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_FINISH_REQUEST;
118130
}
119131
}
120132
}

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
@@ -1339,6 +1339,13 @@ public async Task Reset_DuringRequestBody_Resets_Complete(HttpContext httpContex
13391339
await _resetDuringRequestBodyResetsCts.Task;
13401340
}
13411341

1342+
public async Task SlowOnCompleted(HttpContext context)
1343+
{
1344+
// This shouldn't block the response or the server from shutting down.
1345+
context.Response.OnCompleted(() => Task.Delay(TimeSpan.FromMinutes(5)));
1346+
await context.Response.WriteAsync("SlowOnCompleted");
1347+
}
1348+
13421349
internal static readonly HashSet<(string, StringValues, StringValues)> NullTrailers = new HashSet<(string, StringValues, StringValues)>()
13431350
{
13441351
("NullString", (string)null, (string)null),

0 commit comments

Comments
 (0)