Skip to content

Conversation

@JamesNK
Copy link
Member

@JamesNK JamesNK commented Jul 31, 2022

WebTransport changes started allocating context for every request.
QuicStream.Dispose just calls DisposeAsync but blocks. Call DisposeAsync directly.

@JamesNK JamesNK force-pushed the jamesnk/http3-streamcontext branch from c298fb3 to 6ff4b42 Compare August 2, 2022 07:59
Debug.Assert(streamDirectionFeature != null);
Debug.Assert(streamIdFeature != null);

var context = CreateHttpStreamContext(streamContext);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not generally clear why the changes in this file matter. Are they primarily about avoiding CreateHttpStreamContext(...) in the else portion of CreateHttp3Stream<TContext>(...)❔ (I'm somewhat unsure whether I'm missing another branch where CreateHttpStreamContext(...) is no longer called.)

Separately context and _context are both used below. Could we use slightly more distinct names❔

&& !_serverAborted
&& _shutdownReadReason == null
&& _shutdownWriteReason == null;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Null out _stream inside the lock, I assume DisposeCore is safe outside of the lock.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved it because DisposeAsync can't happen inside a lock.

The stream shouldn't be used anymore because _processingTask is awaited. The one place it could be used, and _stream is accessed in a lock, is Abort.

I've changed Abort so that it takes a local copy of _stream and then checks it for null.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So basically, we need to be able to call _stream.Abort() while _stream.DisposeAsync() is running? And anything else would be invalid since middleware has already completed.

The only problem is that Abort() could in theory still be in the middle of executing while we return the context to the pool because it doesn't do any synchronization. This could be bad if Abort() does something like setting _serverAborted after the QuicStreamContext has already been pushed and poped from the pool and "reset". I don't think Abort() can be thread safe without acquiring the _shutdownLock.

The other thing to make sure of is that we null _stream out before it enters the pool. DisposeAsync() is always awaited by Http3Stream before it calls Dispose() which may pool this QuicStreamContext, so we're good. Maybe putting it at the start of Dispose() before the if (!_connection.TryReturnStream(this)) would make this more obvious.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed:

  • DisposeAsync to call stream.DisposeAsync before the lock, and moved setting _stream to null inside the lock again. I think I like it more now, but I don't think this makes a huge difference as draining will already have happened by other code awaiting WaitForWritesClosedAsync. Because draining has already happened, stream.DisposeAsync should complete immediately.
  • Abort now checks for null _stream and uses lock when determining whether it should run.

_shutdownReason = abortReason;

var resolvedErrorCode = _error ?? 0;
QuicLog.StreamAbort(_log, this, resolvedErrorCode, abortReason.Message);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we produce a different log (or no log?) when _stream is null which would imply the stream had already stopped?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made Abort exit immediately if _stream is null. There is nothing for it to do: no stream to abort and pipes are completed in dispose.

&& !_serverAborted
&& _shutdownReadReason == null
&& _shutdownWriteReason == null;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So basically, we need to be able to call _stream.Abort() while _stream.DisposeAsync() is running? And anything else would be invalid since middleware has already completed.

The only problem is that Abort() could in theory still be in the middle of executing while we return the context to the pool because it doesn't do any synchronization. This could be bad if Abort() does something like setting _serverAborted after the QuicStreamContext has already been pushed and poped from the pool and "reset". I don't think Abort() can be thread safe without acquiring the _shutdownLock.

The other thing to make sure of is that we null _stream out before it enters the pool. DisposeAsync() is always awaited by Http3Stream before it calls Dispose() which may pool this QuicStreamContext, so we're good. Maybe putting it at the start of Dispose() before the if (!_connection.TryReturnStream(this)) would make this more obvious.

@JamesNK JamesNK enabled auto-merge (squash) August 3, 2022 01:30
@JamesNK JamesNK merged commit 942b6ef into main Aug 3, 2022
@JamesNK JamesNK deleted the jamesnk/http3-streamcontext branch August 3, 2022 03:03
@ghost ghost added this to the 7.0-rc1 milestone Aug 3, 2022
@davidfowl davidfowl added the Perf label Aug 26, 2022
@amcasey amcasey added area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions and removed area-runtime labels Jun 6, 2023
@github-actions github-actions bot locked and limited conversation to collaborators Dec 8, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions Perf

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants