Reputation: 43
I am developing a new API in a .NET Core service, the new API is supposed to read a BLOB from SQL table, decompress it using DeflateStream. And then return it (stream it) to the client.
In order not to consume much memory. I am returning a response of type and PushStreamContent so that I could copy the sql stream into the response stream directly without loading the blob in memory. So I ended up with something like that.
return this.ResponseMessage(new HttpResponseMessage
{
Content = new PushStreamContent(async (outStream, httpContent, transportContext) =>
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
await connection.OpenAsync();
using (SqlCommand command = new SqlCommand(query, connection))
{
// The reader needs to be executed with the SequentialAccess behavior to enable network streaming
// Otherwise ReadAsync will buffer the entire BLOB into memory which can cause scalability issues or even OutOfMemoryExceptions
using (SqlDataReader reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess))
{
if (await reader.ReadAsync() && !(await reader.IsDBNullAsync(0)))
{
using (Stream streamToDecompress = reader.GetStream(0))
using (Stream decompressionStream = new DeflateStream(streamToDecompress, CompressionMode.Decompress))
{
// This copyToAsync will take for ever
await decompressionStream.CopyToAsync(outStream);
outStream.close();
return;
}
}
throw new Exception("Couldn't retrieve blob");
}
}
}
},
"application/octet-stream")
});
The problem here is that the step that copies the deflateStream to the response output stream takes for ever as mentioned in the code. Although I tried the same exact method but with writing the stream to a file instead of copying it to the resp stream and it worked like a charm.
So can you guys help me with this?? Am I wrong about using the PushStreamContent? Should I use a different approach? The thing is that I don't want to load the whole Blob in memory, I want to read it and decompress it on the fly. SqlClient Supports streaming blobs and I want to make use of that.
Upvotes: 1
Views: 641
Reputation: 89361
This is a deadlock in PushStreamContent, which I don't pretend to understand. But I repro'd it and changing
await decompressionStream.CopyToAsync(outStream);
to
decompressionStream.CopyTo(outStream);
resolves it.
Here's the full repro:
public ResponseMessageResult Get()
{
var data = new string[] { "value1", "value2" };
var jsonData = Newtonsoft.Json.JsonConvert.SerializeObject(data);
var msSource = new MemoryStream(Encoding.UTF8.GetBytes(jsonData));
var msDest = new MemoryStream();
var compressionStream = new DeflateStream(msDest, CompressionMode.Compress);
msSource.CopyTo(compressionStream);
compressionStream.Close();
var compressedBytes = msDest.ToArray();
var query = "select @bytes buf";
var connectionString = "server=localhost;database=tempdb;integrated security=true";
return this.ResponseMessage(new HttpResponseMessage
{
Content = new PushStreamContent(async (outStream, httpContent, transportContext) =>
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
await connection.OpenAsync();
using (SqlCommand command = new SqlCommand(query, connection))
{
command.Parameters.Add("@bytes", SqlDbType.VarBinary, -1).Value = compressedBytes;
// The reader needs to be executed with the SequentialAccess behavior to enable network streaming
// Otherwise ReadAsync will buffer the entire BLOB into memory which can cause scalability issues or even OutOfMemoryExceptions
using (SqlDataReader reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess))
{
if (await reader.ReadAsync() && !(await reader.IsDBNullAsync(0)))
{
using (Stream streamToDecompress = reader.GetStream(0))
{
//var buf = new MemoryStream();
//streamToDecompress.CopyTo(buf);
//buf.Position = 0;
using (Stream decompressionStream = new DeflateStream(streamToDecompress, CompressionMode.Decompress))
{
// This copyToAsync will take for ever
//await decompressionStream.CopyToAsync(outStream);
decompressionStream.CopyTo(outStream);
outStream.Close();
return;
}
}
}
throw new Exception("Couldn't retrieve blob");
}
}
}
},
"application/octet-stream")
});
}
Upvotes: 1