Skip to content

Batch Fixes - #2048

Merged
mikepizzo merged 3 commits into
OData:masterfrom
mikepizzo:BatchFixes
Feb 23, 2020
Merged

Batch Fixes#2048
mikepizzo merged 3 commits into
OData:masterfrom
mikepizzo:BatchFixes

Conversation

@mikepizzo

@mikepizzo mikepizzo commented Feb 11, 2020

Copy link
Copy Markdown
Contributor
  1. Fix batch dispose issue (error writing to a closed stream)
  2. Fix async batch issues (synchronous write to an asynchronous reader error)

Issues

This pull request fixes issue #2009

Description

In certain batch cases, the stream used to write the response was disposed before all the responses were written, resulting in a closed stream exception.

Also, certain async batch operations called synchronous operations under the covers, resulting in an exception regarding calling synchronous operations after an asynchronous operation.

Checklist (Uncheck if it is not completed)

  • Test cases added
  • Build and test with one-click build and test script passed

1) Fix batch dispose issue (error writing to a closed stream)
2) Fix async batch issues (synchronous write to an asynchronous reader error)
/// <summary>
/// Release the batch stream and underlying resources
/// </summary>
internal void Release()

@xuzhg xuzhg Feb 11, 2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Release() [](start = 22, length = 9)

Detach ? #Resolved

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Well, it's not actually detaching anything; the memory stream is fully contained by the ODataBatchStream; it's just releasing the resources associated with (underlying) stream.

Maybe Free()? Discard? Internal_Dispose?


In reply to: 377796792 [](ancestors = 377796792)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Went with InternalDispose(), since that's really what's happening.


In reply to: 377919530 [](ancestors = 377919530,377796792)

HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, requestUri);
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
HttpContent content = new StringContent(@"
{

@xuzhg xuzhg Feb 11, 2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

                                                                                                    [](start = 13, length = 104)

a lot of whitespaces are needless. Consider to remove all of them? #Resolved

new DefaultODataPathHandler(),
ODataRoutingConventions.CreateDefault(),
configuration.CreateUnbufferedODataBatchHandler());
}

@xuzhg xuzhg Feb 11, 2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

revert these changes? #Resolved


// Act
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, requestUri);
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));

@xuzhg xuzhg Feb 11, 2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json")); [](start = 12, length = 86)

this line is mandatory? #Resolved

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In WebAPI, if you don't specify an accept header, the response is multipart/mixed. We should maybe fix this...


In reply to: 377800365 [](ancestors = 377800365)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated WebAPI to set default to JSON if request is JSON and no accept is specified.


In reply to: 377925870 [](ancestors = 377925870,377800365)

// For responses within a batch, we need to read from the stream after the response is
// written in order to write it to the batch response stream. So we need to ignore the Close()
// and provide an alternate method to release the stream after writing its content to the batch response.
internal class BatchStream : MemoryStream

@xuzhg xuzhg Feb 11, 2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

BatchStream [](start = 19, length = 11)

ODataBatchStream? #Resolved

/// <summary>
/// Release the batch stream and underlying resources
/// </summary>
internal void Release()

@xuzhg xuzhg Feb 11, 2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

internal void Release() [](start = 8, length = 23)

shall we add a protect mechanism?
how about to call twice "Release()"?

Maybe test IsClosed() before call base.Close()? #Resolved

-Case-insensitive header comparison
-Default to JSON Batch response if request is JSON
-Make sure stream is released before reading next request within a batch
-Added/fixed tests for standard and .net core
/// </summary>
/// <param name="writer">The <see cref="ODataBatchWriter"/>.</param>
/// <param name="context">The message context.</param>
public static void WriteMessage(ODataBatchWriter writer, HttpContext context)

@xuzhg xuzhg Feb 12, 2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

atic void WriteMessage(ODataBatchWriter writer, HttpContext con [](start = 17, length = 63)

can we call "WriteMessageAsync" in WriteMessage, or Call "WriteMessage()" in "WriteMessageAsync"? #Resolved

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I got rid of WriteMessage, and instead just created a WriteMessageAsync that took a flag to determine whether the writer was async. When we have a major breaking change release of WebAPI OData I want to make all of this internal; there's no need for this functionality to be public, and it's confusing and broken in its current state.


In reply to: 378431693 [](ancestors = 378431693)

@xuzhg xuzhg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

:shipit:

…hronous writing behavior to avoid possible (though unlikely) breaking change.

Note that any types directly deriving from ODataBatchResponseItem would have to be recompiled due to changes in abstract base class (abstract method now takes a writeAsync parameter).  Seems highly unlikely that other custom types are deriving directly from ODataBatchResponseItem.
In .NET 5, we should clean up the overloads that write to the stream synchronously, and probably make most of this internal (no reason to make the WriteResponseAsync/WriteMessageAsync on ODataBatchResponseItem public).
@mikepizzo
mikepizzo merged commit eeb19f9 into OData:master Feb 23, 2020
@mikepizzo
mikepizzo deleted the BatchFixes branch February 23, 2020 01:44
xuzhg pushed a commit that referenced this pull request May 29, 2026
pull Bot pushed a commit to ehtick/WebApi that referenced this pull request May 29, 2026
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.

4 participants