src/Experimental/src/Eventuous.ElasticSearch/Store/ElasticSerializer.cs
All four async members of ElasticSerializer delegate straight to their synchronous counterparts and hand back an already-completed task:
public Task<object> DeserializeAsync(Type type, Stream stream, CancellationToken cancellationToken = default)
=> Task.FromResult(Deserialize(type, stream));
public Task<T> DeserializeAsync<T>(Stream stream, CancellationToken cancellationToken = default)
=> Task.FromResult(Deserialize<T>(stream));
public Task SerializeAsync<T>(T data, Stream stream, SerializationFormatting formatting = SerializationFormatting.None, CancellationToken cancellationToken = default) {
Serialize(data, stream, formatting);
return Task.CompletedTask;
}
Two consequences:
- The stream I/O blocks the calling thread.
Deserialize reads the response stream synchronously and Serialize writes through a Utf8JsonWriter synchronously. A caller that awaits DeserializeAsync still pays a blocking read, so the async path buys nothing and can starve the thread pool under load.
cancellationToken is accepted and silently discarded on all four members. Callers reasonably expect a long read to be cancellable; it isn't.
Suggested fix
JsonSerializer.DeserializeAsync(stream, type, _options, cancellationToken) for the deserialize path.
JsonSerializer.SerializeAsync(stream, data, _options, cancellationToken) for the serialize path — or keep Utf8JsonWriter and switch to FlushAsync + DisposeAsync.
- Route the non-
PersistedEvent branch of SerializeAsync through builtIn.SerializeAsync rather than builtIn.Serialize.
The synchronous members must stay synchronous — IElasticsearchSerializer declares them that way, so they can't be changed. This issue is only about the async ones.
Blocker
ElasticSerializer has no test coverage — src/Experimental/test/ contains only the Spyglass projects. Round-trip tests should land first (or alongside), covering at minimum a PersistedEvent whose Message is remapped through ITypeMapper, since that nested re-deserialization is the part most likely to break in a rewrite.
Context
Surfaced by the Qodo review bot on #578, which flagged the synchronous I/O on lines that are sync by interface contract and were equally synchronous before that PR. Declined there as out of scope for a disposal fix; the real problem is the async delegation above, which predates it.
Low priority — the package is under src/Experimental/.
src/Experimental/src/Eventuous.ElasticSearch/Store/ElasticSerializer.csAll four async members of
ElasticSerializerdelegate straight to their synchronous counterparts and hand back an already-completed task:Two consequences:
Deserializereads the response stream synchronously andSerializewrites through aUtf8JsonWritersynchronously. A caller that awaitsDeserializeAsyncstill pays a blocking read, so the async path buys nothing and can starve the thread pool under load.cancellationTokenis accepted and silently discarded on all four members. Callers reasonably expect a long read to be cancellable; it isn't.Suggested fix
JsonSerializer.DeserializeAsync(stream, type, _options, cancellationToken)for the deserialize path.JsonSerializer.SerializeAsync(stream, data, _options, cancellationToken)for the serialize path — or keepUtf8JsonWriterand switch toFlushAsync+DisposeAsync.PersistedEventbranch ofSerializeAsyncthroughbuiltIn.SerializeAsyncrather thanbuiltIn.Serialize.The synchronous members must stay synchronous —
IElasticsearchSerializerdeclares them that way, so they can't be changed. This issue is only about the async ones.Blocker
ElasticSerializerhas no test coverage —src/Experimental/test/contains only the Spyglass projects. Round-trip tests should land first (or alongside), covering at minimum aPersistedEventwhoseMessageis remapped throughITypeMapper, since that nested re-deserialization is the part most likely to break in a rewrite.Context
Surfaced by the Qodo review bot on #578, which flagged the synchronous I/O on lines that are sync by interface contract and were equally synchronous before that PR. Declined there as out of scope for a disposal fix; the real problem is the async delegation above, which predates it.
Low priority — the package is under
src/Experimental/.