Hi,
Few months ago, I raised a ticket about a crash in version 3.4.8: https://www.couchbase.com/forums/t/crash-with-3-4-8-nuget-package/36553$
The fix was to remove HttpCompletionOption.ResponseHeadersRead
in .net 4.8 https://review.couchbase.org/c/couchbase-net-client/+/193873
But now, the side effect is that queries runs in synchronous mode: Async queries runs synchronous in .net 4.8 · Issue #369 · couchbaselabs/Linq2Couchbase · GitHub
This is annoying, so I digged and found the root cause of the crash in 3.4.8 and I have a fix
In QueryClient.ExecuteQuery
we declare the HttClient as is:
using var httpClient = CreateHttpClient(options.TimeoutValue);
This means, once we leave the ExecuteQuery
method, the http client will be disposed.
But before we leave the ExecuteQuery method, we keep an instance of the stream in result class
if (serializer is IStreamingTypeDeserializer streamingDeserializer)
{
queryResult = new StreamingQueryResult<T>(stream, streamingDeserializer, ErrorContextFactory);
}
else
{
queryResult = new BlockQueryResult<T>(stream, serializer);
}
And the stream will be reused later for each enumerator loop
The side effect in .net 4.8, when we dispose the http client, it disposes also the stream
The fix is to not dispose the httclient in the ExecuteQuery
method, but pass it to the result and let the result dipsose the client
In QueryClient.ExecuteQuery
, declare the http client as is (remove the using):
var httpClient = CreateHttpClient(options.TimeoutValue);
In QueryClient.ExecuteQuery
, pass the http client to the result
if (serializer is IStreamingTypeDeserializer streamingDeserializer)
{
queryResult = new StreamingQueryResult<T>(httpClient, stream, streamingDeserializer, ErrorContextFactory);
}
else
{
queryResult = new BlockQueryResult<T>(httpClient, stream, serializer);
}
in QueryResultBase
, store the reference of the client
private readonly HttpClient _httpClient;
/// <summary>
/// Creates a new QueryResultBase.
/// </summary>
/// <param name="httpClient">The <see cref="HttpClient"/> holding the request</param>;
/// <param name="responseStream"><see cref="Stream"/> to read.</param>
protected QueryResultBase(HttpClient httpClient, Stream responseStream)
{
_httpClient = httpClient;
ResponseStream = responseStream ?? throw new ArgumentNullException(nameof(responseStream));
}
in QueryResultBase.Dispose
, dispose the client
public virtual void Dispose()
{
ResponseStream?.Dispose();
_httpClient?.Dispose();
}
This is the same logic for search, analytics and views