Skip to content

Potentially missing await of requestBodyTask in Http2Connection #107082

Open
@arekpalinski

Description

@arekpalinski

Description

We have a customized implementation of HttpStream which under the covers reads data from unmanaged memory and writes it as JSON to the request body:

https://github.com/ravendb/ravendb/blob/v5.4/src/Raven.Client/Json/BlittableJsonContent.cs

More specifically we have SingleNodeBatchCommand which uses BlittableJsonContent:

https://github.com/ravendb/ravendb/blob/801a228dbc33eeaee83e288e733d50bdad18f3a5/src/Raven.Client/Documents/Commands/Batches/BatchCommand.cs#L96-L114

This command is used in Raven ETL process to ELT data from one RavenDB database to another. It's used by Raven ETL process which a single, dedicated thread:

https://github.com/ravendb/ravendb/blob/801a228dbc33eeaee83e288e733d50bdad18f3a5/src/Raven.Server/Documents/ETL/Providers/Raven/RavenEtl.cs#L181

The problem is that we're getting randomly crashes, AVE or exceptions when ETL process is stopping while the HTTP request is being processed.

When reproducing it locally we're mostly getting NRE exception due to some internals being disposed (which deals with unmanaged memory hence often resulting in AVE). The stacktrace is always the same:

System.NullReferenceException: 'Object reference not set to an instance of an object.'

 	Sparrow.dll!Sparrow.Compression.VariableSizeEncoding.Read<int>(byte* input, out int offset, out bool success) Line 181	C#
 	Sparrow.dll!Sparrow.Compression.VariableSizeEncoding.Read<int>(byte* input, out int offset) Line 256	C#
 	Sparrow.dll!Sparrow.Json.BlittableJsonReaderBase.ReadVariableSizeInt(int pos, out int offset) Line 179	C#
 	Sparrow.dll!Sparrow.Json.BlittableJsonReaderObject.BlittableJsonReaderObject(int pos, Sparrow.Json.BlittableJsonReaderObject parent, Sparrow.Json.BlittableJsonToken type) Line 163	C#
 	Sparrow.dll!Sparrow.Json.BlittableJsonReaderObject.GetObject(Sparrow.Json.BlittableJsonToken type, int position, out bool isBlittableJsonReader) Line 967	C#
 	Sparrow.dll!Sparrow.Json.BlittableJsonReaderObject.GetPropertyByIndex(int index, ref Sparrow.Json.BlittableJsonReaderObject.PropertyDetails prop, bool addObjectToCache) Line 775	C#
 	Sparrow.dll!Sparrow.Json.AbstractBlittableJsonTextWriter.WriteObject(Sparrow.Json.BlittableJsonReaderObject obj) Line 116	C#
 	Sparrow.dll!Sparrow.Json.AbstractBlittableJsonTextWriter.WriteValue(Sparrow.Json.BlittableJsonToken token, object val) Line 166	C#
 	Sparrow.dll!Sparrow.Json.AbstractBlittableJsonTextWriter.WriteObject(Sparrow.Json.BlittableJsonReaderObject obj) Line 119	C#
 	Sparrow.dll!Sparrow.Json.BlittableJsonTextWriterExtensions.WriteArray(Sparrow.Json.AbstractBlittableJsonTextWriter writer, string name, System.Collections.Generic.IEnumerable<Sparrow.Json.BlittableJsonReaderObject> items) Line 119	C#
	Raven.Client.dll!Raven.Client.Documents.Commands.Batches.SingleNodeBatchCommand.CreateRequest.AnonymousMethod__0(System.IO.Stream stream) Line 104	C#
 	Raven.Client.dll!Raven.Client.Json.BlittableJsonContent.SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext context) Line 33	C#
 	[Resuming Async Method]	
 	System.Private.CoreLib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state)	Unknown
 	System.Private.CoreLib.dll!System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Threading.Tasks.VoidTaskResult>.AsyncStateMachineBox<Raven.Client.Json.BlittableJsonContent.<SerializeToStreamAsync>d__2>.MoveNext(System.Threading.Thread threadPoolThread)	Unknown
 	System.Private.CoreLib.dll!System.Runtime.CompilerServices.TaskAwaiter.OutputWaitEtwEvents.AnonymousMethod__12_0(System.Action innerContinuation, System.Threading.Tasks.Task innerTask)	Unknown
 	System.Private.CoreLib.dll!System.Threading.Tasks.AwaitTaskContinuation.System.Threading.IThreadPoolWorkItem.Execute()	Unknown
 	System.Private.CoreLib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()	Unknown
 	System.Private.CoreLib.dll!System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()	Unknown
 	[Async Call Stack]	
 	[Async] System.Net.Http.dll!System.Net.Http.Http2Connection.Http2Stream.SendRequestBodyAsync(System.Threading.CancellationToken cancellationToken)	Unknown
 	[Async] System.Net.Http.dll!System.Net.Http.HttpConnectionBase.LogExceptions.AnonymousMethod__23_0(System.Threading.Tasks.Task t, object state)	Unknown

We have confirmed that while this is still being processed the ETL thread is already being stopped. So it didn't wait for the request to be completed / aborted.

Based on the above stacktrace it looks here is the relevant piece of code:

else
{
// We received the response headers but the request body hasn't yet finished; this most commonly happens
// when the protocol is being used to enable duplex communication. If the connection is aborted or if we
// get RST or GOAWAY from server, exception will be stored in stream._abortException and propagated up
// to caller if possible while processing response, but make sure that we log any exceptions from this task
// completing asynchronously).
LogExceptions(requestBodyTask);
}

More specifically HttpConnectionBase.LogExceptions() has comment:

/// <summary>Awaits a task, logging any resulting exceptions (which are otherwise ignored).</summary>

but it doesn't look that the task is awaited:

else
{
task.ContinueWith(static (t, state) => LogFaulted((HttpConnectionBase)state!, t), this,
CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
}

Here it what we saw when we got the exception with debugger attached:

image

Is it intentional that requestBodyTask task isn't awaited in that scenario?

Reproduction Steps

We managed to reproduce it locally with the usage of two RavenDB servers and ETL between them. It was reproduced only on HTTP2.

Expected behavior

The requestBodyTask should be awaited.

Actual behavior

Not awaited task causes the continuation of the main thread resulting in crashes.

Regression?

No response

Known Workarounds

No response

Configuration

No response

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-System.Net.HttpdocumentationDocumentation bug or enhancement, does not impact product or test codehelp wanted[up-for-grabs] Good issue for external contributors

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions