Skip to content

Commit 61ed499

Browse files
authored
Remove unconditional async element reading cost for PG arrays (#5304)
1 parent ba9db54 commit 61ed499

2 files changed

Lines changed: 44 additions & 35 deletions

File tree

src/Npgsql/Internal/Converters/ArrayConverter.cs

Lines changed: 31 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -432,21 +432,19 @@ int IElementOperations.GetCollectionCount(object collection, out int[]? lengths)
432432
Size? IElementOperations.GetSizeOrDbNull(SizeContext context, object collection, int[] indices, ref object? writeState)
433433
=> _elemConverter.GetSizeOrDbNull(context.Format, context.BufferRequirement, GetValue(collection, indices), ref writeState);
434434

435-
unsafe ValueTask IElementOperations.Read(bool async, PgReader reader, bool isDbNull, object collection, int[] indices, CancellationToken cancellationToken)
435+
ValueTask IElementOperations.Read(bool async, PgReader reader, bool isDbNull, object collection, int[] indices, CancellationToken cancellationToken)
436436
{
437-
TElement? result;
438-
if (isDbNull)
439-
result = default;
440-
else if (!async)
441-
result = _elemConverter.Read(reader);
442-
else
443-
{
444-
var task = _elemConverter.ReadAsync(reader, cancellationToken);
445-
if (!task.IsCompletedSuccessfully)
446-
return AwaitTask(task.AsTask(), new(this, &SetResult), collection, indices);
437+
if (!isDbNull && async && _elemConverter is PgStreamingConverter<TElement> streamingConverter)
438+
return ReadAsync(streamingConverter, reader, collection, indices, cancellationToken);
447439

448-
result = task.Result;
449-
}
440+
SetValue(collection, indices, isDbNull ? default : _elemConverter.Read(reader));
441+
return new();
442+
}
443+
444+
unsafe ValueTask ReadAsync(PgStreamingConverter<TElement> converter, PgReader reader, object collection, int[] indices, CancellationToken cancellationToken)
445+
{
446+
if (converter.ReadAsyncAsTask(reader, cancellationToken, out var result) is { } task)
447+
return AwaitTask(task, new(this, &SetResult), collection, indices);
450448

451449
SetValue(collection, indices, result);
452450
return new();
@@ -505,34 +503,32 @@ int IElementOperations.GetCollectionCount(object collection, out int[]? lengths)
505503
Size? IElementOperations.GetSizeOrDbNull(SizeContext context, object collection, int[] indices, ref object? writeState)
506504
=> _elemConverter.GetSizeOrDbNull(context.Format, context.BufferRequirement, GetValue(collection, indices[0]), ref writeState);
507505

508-
unsafe ValueTask IElementOperations.Read(bool async, PgReader reader, bool isDbNull, object collection, int[] indices, CancellationToken cancellationToken)
506+
ValueTask IElementOperations.Read(bool async, PgReader reader, bool isDbNull, object collection, int[] indices, CancellationToken cancellationToken)
509507
{
510508
Debug.Assert(indices.Length is 1);
511-
TElement? result;
512-
if (isDbNull)
513-
result = default;
514-
else if (!async)
515-
result = _elemConverter.Read(reader);
516-
else
517-
{
518-
var task = _elemConverter.ReadAsync(reader, cancellationToken);
519-
if (!task.IsCompletedSuccessfully)
520-
return AwaitTask(task.AsTask(), new(this, &SetResult), collection, indices);
509+
if (!isDbNull && async && _elemConverter is PgStreamingConverter<TElement> streamingConverter)
510+
return ReadAsync(streamingConverter, reader, collection, indices, cancellationToken);
521511

522-
result = task.Result;
523-
}
524-
525-
SetValue(collection, indices[0], result);
512+
SetValue(collection, indices[0], isDbNull ? default : _elemConverter.Read(reader));
526513
return new();
527-
528-
// Using .Result on ValueTask is equivalent to GetAwaiter().GetResult(), this removes TaskAwaiter<TElement> rooting.
529-
static void SetResult(Task task, object collection, int[] indices)
530-
{
531-
Debug.Assert(task is Task<TElement>);
532-
SetValue(collection, indices[0], new ValueTask<TElement>(Unsafe.As<Task<TElement>>(task)).Result);
533-
}
534514
}
535515

516+
unsafe ValueTask ReadAsync(PgStreamingConverter<TElement> converter, PgReader reader, object collection, int[] indices, CancellationToken cancellationToken)
517+
{
518+
if (converter.ReadAsyncAsTask(reader, cancellationToken, out var result) is { } task)
519+
return AwaitTask(task, new(this, &SetResult), collection, indices);
520+
521+
SetValue(collection, indices[0], result);
522+
return new();
523+
524+
// Using .Result on ValueTask is equivalent to GetAwaiter().GetResult(), this removes TaskAwaiter<TElement> rooting.
525+
static void SetResult(Task task, object collection, int[] indices)
526+
{
527+
Debug.Assert(task is Task<TElement>);
528+
SetValue(collection, indices[0], new ValueTask<TElement>(Unsafe.As<Task<TElement>>(task)).Result);
529+
}
530+
}
531+
536532
ValueTask IElementOperations.Write(bool async, PgWriter writer, object collection, int[] indices, CancellationToken cancellationToken)
537533
{
538534
Debug.Assert(indices.Length is 1);

src/Npgsql/Internal/PgStreamingConverter.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,19 @@ public override bool CanConvert(DataFormat format, out BufferRequirements buffer
1616
return format is DataFormat.Binary;
1717
}
1818

19+
// Workaround for trimming https://github.com/dotnet/runtime/issues/92850#issuecomment-1744521361
20+
internal Task<T>? ReadAsyncAsTask(PgReader reader, CancellationToken cancellationToken, out T result)
21+
{
22+
var task = ReadAsync(reader, cancellationToken);
23+
if (task.IsCompletedSuccessfully)
24+
{
25+
result = task.Result;
26+
return null;
27+
}
28+
result = default!;
29+
return task.AsTask();
30+
}
31+
1932
internal sealed override unsafe ValueTask<object> ReadAsObject(
2033
bool async, PgReader reader, CancellationToken cancellationToken)
2134
{

0 commit comments

Comments
 (0)