Runtime: XDocument.SaveAsync has a blocking call

Created on 6 May 2019  路  9Comments  路  Source: dotnet/runtime

XDocument.SaveAsync has a blocking call. In ASP.NET Core 3 this is not allowed (by default).

````
System.Net.Http.HttpRequestException: Error while copying content to a stream. ---> System.IO.IOException: ---> System.InvalidOperationException: Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true.

at Microsoft.AspNetCore.TestHost.ResponseStream.Write(Byte[] buffer, Int32 offset, Int32 count)
at System.Xml.XmlUtf8RawTextWriter.FlushBuffer()
at System.Xml.XmlUtf8RawTextWriter.Flush()
at System.Xml.XmlWellFormedWriter.Close()
at System.Xml.XmlWriter.Dispose(Boolean disposing)
at System.Xml.XmlAsyncCheckWriter.Dispose(Boolean disposing)
at System.Xml.XmlWriter.Dispose()
at System.Xml.Linq.XDocument.SaveAsync(Stream stream, SaveOptions options, CancellationToken cancellationToken)
at MyCode(Stream stream, XDocument document, CancellationToken cancellationToken)
````

I think SaveAsync should call FlushAsync before calling Dispose (or maybe XmlWriter should implement IAsyncDisposable).

```c#
public async Task SaveAsync(Stream stream, SaveOptions options, CancellationToken cancellationToken)
{
XmlWriterSettings ws = GetXmlWriterSettings(options);

        ws.Async = true;

        if (_declaration != null && !string.IsNullOrEmpty(_declaration.Encoding))
        {
            try
            {
                ws.Encoding = Encoding.GetEncoding(_declaration.Encoding);
            }
            catch (ArgumentException)
            {
            }
        }

        using (XmlWriter w = XmlWriter.Create(stream, ws))
        {
            await WriteToAsync(w, cancellationToken).ConfigureAwait(false);

            // I think we should add FlushAsync here
            await w.FlushAsync().ConfigureAwait(false);
        }
    }

```

area-System.Xml

Most helpful comment

@karelz @davidfowl
Was this really ever solved in .NET Core 3?
Asking because I'm getting the exact same error with both XElement and XDocument. Even tried with code like

using (var w = XmlWriter.Create(response.Body, new XmlWriterSettings { Async = true }))
{
    await xelement.WriteToAsync(w, CancellationToken.None).ConfigureAwait(false);
    await w.FlushAsync().ConfigureAwait(false);
} // <- throws here

and it throws on Dispose of XmlWritter - it still calls synchronous Flush, just like @meziantou reported.

All 9 comments

Nice! 馃槃 Yes we should be doing an async flush here.

@karelz @davidfowl
Was this really ever solved in .NET Core 3?
Asking because I'm getting the exact same error with both XElement and XDocument. Even tried with code like

using (var w = XmlWriter.Create(response.Body, new XmlWriterSettings { Async = true }))
{
    await xelement.WriteToAsync(w, CancellationToken.None).ConfigureAwait(false);
    await w.FlushAsync().ConfigureAwait(false);
} // <- throws here

and it throws on Dispose of XmlWritter - it still calls synchronous Flush, just like @meziantou reported.

Looks like that PR didn't add any tests confirming. We can take another look.

@stephentoub Also note that XElement has a separate implementation for some reason and that is still as it was before this PR. I feel that XmlWriter always calls a synchronous Flush when disposed, regardless whether it was already flushed. Just a feeling though.

It probably calls it on nested XmlWritter as it disposes it in Dispose(bool) method.

Should I try and create unit tests?

Should I try and create unit tests?

Sure, that'd be helpful, thanks!

Crated a PR with simple tests. I think the solution would be to modify both XmlUtf8RawTextWriter and XmlWellFormedWriter at minimum (and I guess other subclasses) so they would call FlushAsync when settings.Async == true.
Perhaps a better solution would be to add CloseAsync method to XmlWriter, since FlushAsync within Close can't be awaited. CloseAsnyc would be called instead of Close each time XmlWriter is async.

馃憦

Was this page helpful?
0 / 5 - 0 ratings