Skip to content

Redux: Fix hangup of execution of queries like "COPY TO STDOUT" in asynchronous mode (now with tests)#163

Open
esabol wants to merge 1 commit intobucardo:masterfrom
esabol:fix-hanging-async-copy
Open

Redux: Fix hangup of execution of queries like "COPY TO STDOUT" in asynchronous mode (now with tests)#163
esabol wants to merge 1 commit intobucardo:masterfrom
esabol:fix-hanging-async-copy

Conversation

@esabol
Copy link
Copy Markdown
Collaborator

@esabol esabol commented Mar 14, 2026

This is an updated version of @true-alex's pull request #101. It is meant to address issue #98.

$dbh->do("COPY (select * from unnest(ARRAY[1,2,3,4,5,6,7,8]::int[]) as mytable(a)) TO STDOUT", {pg_async => PG_ASYNC}); still hangs with DBD::Pg 3.19.0.

I have applied @true-alex's proposed fix to the latest master branch and added tests to t/08async.t.

The proposed fix indeed prevents hanging the process, but I don't think the return value of $dbh->pg_result() is correct. Once $dbh->pg_ready becomes true after executing $dbh->do("COPY (select * from unnest(ARRAY[1,2,3,4,5,6,7,8]::int[]) as mytable(a)) TO STDOUT", {pg_async => PG_ASYNC});, $dbh->pg_result() currently returns -1, but I think it should return 0 (the number of rows affected). Am I right about that, @turnstep ?

Marking PR as a draft for now.

@turnstep
Copy link
Copy Markdown
Contributor

Yes, it should return 0

@true-alex
Copy link
Copy Markdown

Thanks for adding the tests. I tried adding them myself, but haven't had any success yet.

pg_result returns the number of affected rows, just like do. When executing async copy, we don't know the number of affected rows, so the call returns -1. This behavior is similar to the do method:
returns -1 if the number of rows is unknown or not available.

@esabol
Copy link
Copy Markdown
Collaborator Author

esabol commented Mar 16, 2026

@true-alex : Sure, that makes sense while $dbh->pg_ready returns false, but shouldn't $dbh->pg_result return zero once $dbh->pg_ready returns true?

@esabol esabol changed the title Redux: Fix hangup of execution of queries like "COPY TO STDIN" in asynchronous mode (now with tests) Redux: Fix hangup of execution of queries like "COPY TO STOUT" in asynchronous mode (now with tests) Mar 16, 2026
@esabol
Copy link
Copy Markdown
Collaborator Author

esabol commented Mar 16, 2026

I think I just realized the title of the pull request was wrong? I just changed "COPY TO STDIN" to "COPY TO STDOUT".

@esabol esabol changed the title Redux: Fix hangup of execution of queries like "COPY TO STOUT" in asynchronous mode (now with tests) Redux: Fix hangup of execution of queries like "COPY TO STDOUT" in asynchronous mode (now with tests) Mar 16, 2026
@jjn1056
Copy link
Copy Markdown

jjn1056 commented Mar 29, 2026

Chiming in on the return value question. After tracing through the code, I think -1 is correct and consistent. Here's why:

Synchronous COPY already returns -1

When you run COPY synchronously, dbd_st_execute hits the PGRES_COPY_OUT/IN/BOTH case and returns -1 (dbdimp.c line 3897). DBI's do() passes that through:

# Synchronous — returns -1
my $ret = $dbh->do("COPY mytable TO STDOUT");
# $ret is -1

If pg_result() returned 0 for async COPY, the two paths would give different answers for the same operation:

# Async — would return 0 (inconsistent)
$dbh->do("COPY mytable TO STDOUT", {pg_async => PG_ASYNC});
# ... pg_ready() ...
my $ret = $dbh->pg_result();
# $ret would be 0, but sync do() returns -1 for the same statement?

-1 has the right DBI semantics

DBI defines -1 as "number of rows is unknown or not available." That's precisely the situation — a COPY statement starts a data transfer, it doesn't affect rows. The actual row count only becomes known after draining with pg_getcopydata. Returning 0 would mean "zero rows affected," which isn't accurate either.

The code is already consistent

pg_db_result already sets rows = -1 for COPY results in its switch statement (dbdimp.c line 5735). The 3-line fix in this PR correctly lets that value escape the PQgetResult loop instead of hanging. No additional changes needed.

Suggested test fix

The TODO test should just assert -1:

is ($res, -1, "pg_result() after async COPY TO STDOUT returns -1 (rows unknown during COPY)");

@esabol
Copy link
Copy Markdown
Collaborator Author

esabol commented Mar 29, 2026

Thanks for your thoughts on this, @jjn1056. I found your points persuasive, especially this:

DBI defines -1 as "number of rows is unknown or not available." That's precisely the situation — a COPY statement starts a data transfer, it doesn't affect rows. The actual row count only becomes known after draining with pg_getcopydata. Returning 0 would mean "zero rows affected," which isn't accurate either.

What do you think, @turnstep ?

@turnstep
Copy link
Copy Markdown
Contributor

turnstep commented Mar 29, 2026 via email

This is a redux GitHub PR bucardo#101 by true-alex, rebased and with test cases added.
See GitHub issue bucardo#98.
@esabol esabol force-pushed the fix-hanging-async-copy branch from a368147 to 5eb1d23 Compare March 29, 2026 22:43
@esabol esabol marked this pull request as ready for review March 29, 2026 22:58
@esabol
Copy link
Copy Markdown
Collaborator Author

esabol commented Mar 29, 2026

OK, I have rebased and squashed commits. CI pass is clean. Ready for review.

@turnstep : Let me know if you want any changes to the tests.

@esabol esabol requested a review from turnstep March 29, 2026 22:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants