Skip to content

Implement PipeBody Features and add to HttpContext #6394

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jan 9, 2019

Conversation

jkotalik
Copy link
Contributor

@jkotalik jkotalik commented Jan 4, 2019

For #3972.
Add more Stream/Pipe interleaving test to verify stream and pipe can be read from/ written two sequentially.

// If the Response.Body has been updated, recreate the pipeWriter
(_pipeWriter is StreamPipeWriter writer && !object.ReferenceEquals(writer.InnerStream, HttpResponseFeature.Body)))
{
_pipeWriter = new StreamPipeWriter(HttpResponseFeature.Body);
Copy link
Member

@davidfowl davidfowl Jan 5, 2019

Choose a reason for hiding this comment

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

In this case, do we lose the old reference to the pipe? Who is responsible for cleaning it up? Can this leak memory? Does it matter?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tough situation. Someone can keep a reference to the BodyPipe like:

  context.Response.Body = new MemoryStream();
  var canBeDisposed = context.Response.BodyPipe;
  context.Response.Body = Stream.Null;
  var nonDisposed = context.Response.BodyPipe;

Should we dispose canBeDisposed in this situation? I don't think we would dispose the stream in this situation, so I don't think we should call dispose here.

Copy link
Member

Choose a reason for hiding this comment

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

Considering we're implicitly constructing the StreamPipeWriter, we should dispose it. I don't think we can expect developers to know they might have dispose the old response body StreamPipeWriter just because they replaced the response body Stream.

In this case it's not trivial to track who constructed the _pipeWriter, but we should try. It's also probably a good idea to fail any I/O being done on the old _pipeWriter instead of closing in gracefully.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this case it's not trivial to track who constructed the _pipeWriter, but we should try.

I think the tracking needs to exist inside of the feature. I'll take a stab at something.

It's also probably a good idea to fail any I/O being done on the old _pipeWriter instead of closing in gracefully.

You are effectively asking for us to call Abort on the underlying stream, correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also, what happens if someone wraps the BodyPipe with another PipeReader/Writer. For example, if we were to reimplement ResponseCompression with Pipes, we would do:

ctx.Response.BodyPipe = new ResponseCompressionPipeWriter(ctx.Response.BodyPipe);

which would fail because the BodyPipe is now disposed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We will now call HttpResponse.RegisterForDispose() for all adapters that we set.

@davidfowl
Copy link
Member

@jkotalik @halter73 @Tratcher servers have to re-implement this logic https://github.com/aspnet/AspNetCore/pull/6394/files#diff-f773336ba05e72446c3f058e83ca3bc4R30 yeah? If a server implements these features it has to handle the wrapping and unwrapping manually. These features will only be used by Http.Sys server? Is that correct? (Assuming we do native pipes in both IIS and Kestrel).

@Tratcher
Copy link
Member

Tratcher commented Jan 5, 2019

@davidfowl Kestrel, IIS, and TestServer need to implement this feature but I expect they'd do so in reverse. Pipe would be the default and Stream the fallback. And yes, the implementation here will primarily be used for Http.Sys.

}
set
{
_pipeReader = value ?? throw new ArgumentNullException(nameof(value));
Copy link
Member

Choose a reason for hiding this comment

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

Why throw when setting a null request body pipe? We don't throw when setting a null request body stream do we?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've bounced back and forth between this a few times. There are a few things we need to think about:

When setting the BodyPipe, we should update Body accordingly. When someone sets BodyPipe to null, either we can throw or set both Body and BodyPipe to null. Today, when someone sets Body to null, it remains null.

So I think I agree with you.

Copy link
Member

Choose a reason for hiding this comment

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

Body should never be set to null. Stream.Null maybe, but not null. No we don't throw for that at the moment but in hindsight we should have.

I don't see a scenario for setting pipe to null either.

// If the Response.Body has been updated, recreate the pipeWriter
(_pipeWriter is StreamPipeWriter writer && !object.ReferenceEquals(writer.InnerStream, HttpResponseFeature.Body)))
{
_pipeWriter = new StreamPipeWriter(HttpResponseFeature.Body);
Copy link
Member

Choose a reason for hiding this comment

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

Considering we're implicitly constructing the StreamPipeWriter, we should dispose it. I don't think we can expect developers to know they might have dispose the old response body StreamPipeWriter just because they replaced the response body Stream.

In this case it's not trivial to track who constructed the _pipeWriter, but we should try. It's also probably a good idea to fail any I/O being done on the old _pipeWriter instead of closing in gracefully.

@Tratcher
Copy link
Member

Tratcher commented Jan 7, 2019

Offline discussion on Dispose: You can't. Consider the decorator example for something like response compression. It wraps the inner stream with its own that does the transformation and reassigns the response body field. Then it undoes that wrapping at the end of the response. The same style wrapping should be possible for pipes, so the original value must be kept alive.

@halter73
Copy link
Member

halter73 commented Jan 7, 2019

Offline discussion on Dispose: You can't.

You can and you must if you don't want to leak pooled memory.

When we construct a StreamPipeReader/Writer, we've decided that the body Stream is the source of truth. This only wraps StreamPipeReader/Writers, not arbitrary pipes, so there's no risk of this replacing a transformative pipe.

@jkotalik
Copy link
Contributor Author

jkotalik commented Jan 7, 2019

@halter73 see above. Thoughts on adding it to RegisterForDispose.

@Tratcher
Copy link
Member

Tratcher commented Jan 7, 2019

@halter73 we worked out a cleaner solution: register the pipe for dispose on creation, not on replacement.

else if (!object.ReferenceEquals(_internalPipeWriter.InnerStream, _context.Response.Body))
{
_internalPipeWriter = new StreamPipeWriter(_context.Response.Body);
_context.Response.RegisterForDispose(_internalPipeWriter);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We have to call RegisterForDispose here because by default the DefaultHttpContext doesn't call Uninitialize on the Request and Response features. https://github.com/aspnet/AspNetCore/blob/master/src/Http/Http/src/DefaultHttpContext.cs#L197.

I'm still thinking about how to write a test for this.

@jkotalik jkotalik dismissed halter73’s stale review January 8, 2019 17:14

Resolved offline.

@jkotalik jkotalik force-pushed the jkotalik/httpContextPipes branch from fef445a to 9f1969e Compare January 9, 2019 02:56
@jkotalik jkotalik closed this Jan 9, 2019
@jkotalik jkotalik reopened this Jan 9, 2019
@jkotalik jkotalik merged commit 5541a7a into master Jan 9, 2019
@jkotalik jkotalik deleted the jkotalik/httpContextPipes branch January 9, 2019 05:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants