Skip to content

Commit af6676e

Browse files
authored
Optimize NpgsqlDataReader.NextResult async state machine (#6638)
1 parent 7f18869 commit af6676e

1 file changed

Lines changed: 52 additions & 45 deletions

File tree

src/Npgsql/NpgsqlDataReader.cs

Lines changed: 52 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -472,50 +472,7 @@ async Task<bool> NextResult(bool async, bool isConsuming = false, CancellationTo
472472

473473
if ((Command.WrappingBatch is not null || StatementIndex is 0) && Command.InternalBatchCommands[StatementIndex] is { HasOutputParameters: true } command)
474474
{
475-
// If output parameters are present and this is the first row of the resultset,
476-
// we must always read it in non-sequential mode because it will be traversed twice (once
477-
// here for the parameters, then as a regular row).
478-
msg = await Connector.ReadMessage(async, dataRowLoadingMode: DataRowLoadingMode.NonSequential).ConfigureAwait(false);
479-
ProcessMessage(msg);
480-
if (msg.Code == BackendMessageCode.DataRow)
481-
{
482-
Debug.Assert(RowDescription != null);
483-
Debug.Assert(State == ReaderState.BeforeResult);
484-
485-
try
486-
{
487-
// Temporarily set our state to InResult and non-sequential to allow us to read the values, and in any order.
488-
var isSequential = _isSequential;
489-
var currentPosition = Buffer.ReadPosition;
490-
State = ReaderState.InResult;
491-
_isSequential = false;
492-
try
493-
{
494-
command.PopulateOutputParameters(this, _commandLogger);
495-
496-
// On success we want to revert any row and column state for the user to be able to read the same row again.
497-
if (async)
498-
await PgReader.CommitAsync().ConfigureAwait(false);
499-
else
500-
PgReader.Commit();
501-
502-
State = ReaderState.BeforeResult; // Set the state back
503-
Buffer.ReadPosition = currentPosition; // Restore position
504-
_column = -1;
505-
}
506-
finally
507-
{
508-
// To be on the safe side we always revert this CommandBehavior state change, including on failure.
509-
_isSequential = isSequential;
510-
}
511-
}
512-
catch (Exception e)
513-
{
514-
// TODO: ideally we should flow down to global exception filter and consume there
515-
await Consume(async, firstException: e).ConfigureAwait(false);
516-
throw;
517-
}
518-
}
475+
msg = await ReadMessageWithOutputParameters(async, command).ConfigureAwait(false);
519476
}
520477
else
521478
{
@@ -647,6 +604,56 @@ async ValueTask ConsumeResultSet(bool async)
647604
}
648605
}
649606

607+
async ValueTask<IBackendMessage> ReadMessageWithOutputParameters(bool async, NpgsqlBatchCommand command)
608+
{
609+
// If output parameters are present and this is the first row of the resultset,
610+
// we must always read it in non-sequential mode because it will be traversed twice (once
611+
// here for the parameters, then as a regular row).
612+
var msg = await Connector.ReadMessage(async, dataRowLoadingMode: DataRowLoadingMode.NonSequential).ConfigureAwait(false);
613+
ProcessMessage(msg);
614+
if (msg.Code == BackendMessageCode.DataRow)
615+
{
616+
Debug.Assert(RowDescription != null);
617+
Debug.Assert(State == ReaderState.BeforeResult);
618+
619+
try
620+
{
621+
// Temporarily set our state to InResult and non-sequential to allow us to read the values, and in any order.
622+
var isSequential = _isSequential;
623+
var currentPosition = Buffer.ReadPosition;
624+
State = ReaderState.InResult;
625+
_isSequential = false;
626+
try
627+
{
628+
command.PopulateOutputParameters(this, _commandLogger);
629+
630+
// On success we want to revert any row and column state for the user to be able to read the same row again.
631+
if (async)
632+
await PgReader.CommitAsync().ConfigureAwait(false);
633+
else
634+
PgReader.Commit();
635+
636+
State = ReaderState.BeforeResult; // Set the state back
637+
Buffer.ReadPosition = currentPosition; // Restore position
638+
_column = -1;
639+
}
640+
finally
641+
{
642+
// To be on the safe side we always revert this CommandBehavior state change, including on failure.
643+
_isSequential = isSequential;
644+
}
645+
}
646+
catch (Exception e)
647+
{
648+
// TODO: ideally we should flow down to global exception filter and consume there
649+
await Consume(async, firstException: e).ConfigureAwait(false);
650+
throw;
651+
}
652+
}
653+
654+
return msg;
655+
}
656+
650657
/// <summary>
651658
/// Note that in SchemaOnly mode there are no resultsets, and we read nothing from the backend (all
652659
/// RowDescriptions have already been processed and are available)
@@ -971,7 +978,7 @@ public override int FieldCount
971978
/// Consumes all result sets for this reader, leaving the connector ready for sending and processing further
972979
/// queries
973980
/// </summary>
974-
async Task Consume(bool async, Exception? firstException = null)
981+
async ValueTask Consume(bool async, Exception? firstException = null)
975982
{
976983
var exceptions = firstException is null ? null : new List<Exception> { firstException };
977984

0 commit comments

Comments
 (0)