Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit b71a9cb

Browse files
committed
Fix libpq state machine in pipeline mode
The original coding required that PQpipelineSync had been called before the first call to PQgetResult, and failure to do that would result in an unexpected NULL result being returned. Fix by setting the right state when a query is sent, rather than leaving it unchanged and having PQpipelineSync apply the necessary state change. A new test case to verify the behavior is added, which relies on the new PQsendFlushRequest() function added by commit a719232. Backpatch to 14, where pipeline mode was added. Reported-by: Boris Kolpackov <boris@codesynthesis.com> Author: Álvaro Herrera <alvherre@alvh.no-ip.org> Discussion: https://postgr.es/m/boris.20210616110321@codesynthesis.com
1 parent a719232 commit b71a9cb

File tree

4 files changed

+188
-15
lines changed

4 files changed

+188
-15
lines changed

src/interfaces/libpq/fe-exec.c

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,8 +1375,7 @@ PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
13751375

13761376
/* OK, it's launched! */
13771377
pqAppendCmdQueueEntry(conn, entry);
1378-
if (conn->pipelineStatus == PQ_PIPELINE_OFF)
1379-
conn->asyncStatus = PGASYNC_BUSY;
1378+
conn->asyncStatus = PGASYNC_BUSY;
13801379
return 1;
13811380

13821381
sendFailed:
@@ -1513,8 +1512,7 @@ PQsendPrepare(PGconn *conn,
15131512

15141513
pqAppendCmdQueueEntry(conn, entry);
15151514

1516-
if (conn->pipelineStatus == PQ_PIPELINE_OFF)
1517-
conn->asyncStatus = PGASYNC_BUSY;
1515+
conn->asyncStatus = PGASYNC_BUSY;
15181516

15191517
/*
15201518
* Give the data a push (in pipeline mode, only if we're past the size
@@ -1817,8 +1815,7 @@ PQsendQueryGuts(PGconn *conn,
18171815

18181816
/* OK, it's launched! */
18191817
pqAppendCmdQueueEntry(conn, entry);
1820-
if (conn->pipelineStatus == PQ_PIPELINE_OFF)
1821-
conn->asyncStatus = PGASYNC_BUSY;
1818+
conn->asyncStatus = PGASYNC_BUSY;
18221819
return 1;
18231820

18241821
sendFailed:
@@ -2448,8 +2445,7 @@ PQsendDescribe(PGconn *conn, char desc_type, const char *desc_target)
24482445

24492446
/* OK, it's launched! */
24502447
pqAppendCmdQueueEntry(conn, entry);
2451-
if (conn->pipelineStatus == PQ_PIPELINE_OFF)
2452-
conn->asyncStatus = PGASYNC_BUSY;
2448+
conn->asyncStatus = PGASYNC_BUSY;
24532449
return 1;
24542450

24552451
sendFailed:
@@ -3084,12 +3080,7 @@ PQpipelineSync(PGconn *conn)
30843080
*/
30853081
if (PQflush(conn) < 0)
30863082
goto sendFailed;
3087-
3088-
/*
3089-
* Call pqPipelineProcessQueue so the user can call start calling
3090-
* PQgetResult.
3091-
*/
3092-
pqPipelineProcessQueue(conn);
3083+
conn->asyncStatus = PGASYNC_BUSY;
30933084

30943085
return 1;
30953086

src/test/modules/libpq_pipeline/libpq_pipeline.c

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,93 @@ test_multi_pipelines(PGconn *conn)
230230
fprintf(stderr, "ok\n");
231231
}
232232

233+
/*
234+
* Test behavior when a pipeline dispatches a number of commands that are
235+
* not flushed by a sync point.
236+
*/
237+
static void
238+
test_nosync(PGconn *conn)
239+
{
240+
int numqueries = 10;
241+
int results = 0;
242+
int sock = PQsocket(conn);
243+
244+
fprintf(stderr, "nosync... ");
245+
246+
if (sock < 0)
247+
pg_fatal("invalid socket");
248+
249+
if (PQenterPipelineMode(conn) != 1)
250+
pg_fatal("could not enter pipeline mode");
251+
for (int i = 0; i < numqueries; i++)
252+
{
253+
fd_set input_mask;
254+
struct timeval tv;
255+
256+
if (PQsendQueryParams(conn, "SELECT repeat('xyzxz', 12)",
257+
0, NULL, NULL, NULL, NULL, 0) != 1)
258+
pg_fatal("error sending select: %s", PQerrorMessage(conn));
259+
PQflush(conn);
260+
261+
/*
262+
* If the server has written anything to us, read (some of) it now.
263+
*/
264+
FD_ZERO(&input_mask);
265+
FD_SET(sock, &input_mask);
266+
tv.tv_sec = 0;
267+
tv.tv_usec = 0;
268+
if (select(sock + 1, &input_mask, NULL, NULL, &tv) < 0)
269+
{
270+
fprintf(stderr, "select() failed: %s\n", strerror(errno));
271+
exit_nicely(conn);
272+
}
273+
if (FD_ISSET(sock, &input_mask) && PQconsumeInput(conn) != 1)
274+
pg_fatal("failed to read from server: %s", PQerrorMessage(conn));
275+
}
276+
277+
/* tell server to flush its output buffer */
278+
if (PQsendFlushRequest(conn) != 1)
279+
pg_fatal("failed to send flush request");
280+
PQflush(conn);
281+
282+
/* Now read all results */
283+
for (;;)
284+
{
285+
PGresult *res;
286+
287+
res = PQgetResult(conn);
288+
289+
/* NULL results are only expected after TUPLES_OK */
290+
if (res == NULL)
291+
pg_fatal("got unexpected NULL result after %d results", results);
292+
293+
/* We expect exactly one TUPLES_OK result for each query we sent */
294+
if (PQresultStatus(res) == PGRES_TUPLES_OK)
295+
{
296+
PGresult *res2;
297+
298+
/* and one NULL result should follow each */
299+
res2 = PQgetResult(conn);
300+
if (res2 != NULL)
301+
pg_fatal("expected NULL, got %s",
302+
PQresStatus(PQresultStatus(res2)));
303+
PQclear(res);
304+
results++;
305+
306+
/* if we're done, we're done */
307+
if (results == numqueries)
308+
break;
309+
310+
continue;
311+
}
312+
313+
/* anything else is unexpected */
314+
pg_fatal("got unexpected %s\n", PQresStatus(PQresultStatus(res)));
315+
}
316+
317+
fprintf(stderr, "ok\n");
318+
}
319+
233320
/*
234321
* When an operation in a pipeline fails the rest of the pipeline is flushed. We
235322
* still have to get results for each pipeline item, but the item will just be
@@ -1237,6 +1324,7 @@ print_test_list(void)
12371324
{
12381325
printf("disallowed_in_pipeline\n");
12391326
printf("multi_pipelines\n");
1327+
printf("nosync\n");
12401328
printf("pipeline_abort\n");
12411329
printf("pipelined_insert\n");
12421330
printf("prepared\n");
@@ -1334,6 +1422,8 @@ main(int argc, char **argv)
13341422
test_disallowed_in_pipeline(conn);
13351423
else if (strcmp(testname, "multi_pipelines") == 0)
13361424
test_multi_pipelines(conn);
1425+
else if (strcmp(testname, "nosync") == 0)
1426+
test_nosync(conn);
13371427
else if (strcmp(testname, "pipeline_abort") == 0)
13381428
test_pipeline_abort(conn);
13391429
else if (strcmp(testname, "pipelined_insert") == 0)

src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
{
2727
my @extraargs = ('-r', $numrows);
2828
my $cmptrace = grep(/^$testname$/,
29-
qw(simple_pipeline multi_pipelines prepared singlerow
29+
qw(simple_pipeline nosync multi_pipelines prepared singlerow
3030
pipeline_abort transaction disallowed_in_pipeline)) > 0;
3131

3232
# For a bunch of tests, generate a libpq trace file too.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
F 34 Parse "" "SELECT repeat('xyzxz', 12)" 0
2+
F 14 Bind "" "" 0 0 1 0
3+
F 6 Describe P ""
4+
F 9 Execute "" 0
5+
F 34 Parse "" "SELECT repeat('xyzxz', 12)" 0
6+
F 14 Bind "" "" 0 0 1 0
7+
F 6 Describe P ""
8+
F 9 Execute "" 0
9+
F 34 Parse "" "SELECT repeat('xyzxz', 12)" 0
10+
F 14 Bind "" "" 0 0 1 0
11+
F 6 Describe P ""
12+
F 9 Execute "" 0
13+
F 34 Parse "" "SELECT repeat('xyzxz', 12)" 0
14+
F 14 Bind "" "" 0 0 1 0
15+
F 6 Describe P ""
16+
F 9 Execute "" 0
17+
F 34 Parse "" "SELECT repeat('xyzxz', 12)" 0
18+
F 14 Bind "" "" 0 0 1 0
19+
F 6 Describe P ""
20+
F 9 Execute "" 0
21+
F 34 Parse "" "SELECT repeat('xyzxz', 12)" 0
22+
F 14 Bind "" "" 0 0 1 0
23+
F 6 Describe P ""
24+
F 9 Execute "" 0
25+
F 34 Parse "" "SELECT repeat('xyzxz', 12)" 0
26+
F 14 Bind "" "" 0 0 1 0
27+
F 6 Describe P ""
28+
F 9 Execute "" 0
29+
F 34 Parse "" "SELECT repeat('xyzxz', 12)" 0
30+
F 14 Bind "" "" 0 0 1 0
31+
F 6 Describe P ""
32+
F 9 Execute "" 0
33+
F 34 Parse "" "SELECT repeat('xyzxz', 12)" 0
34+
F 14 Bind "" "" 0 0 1 0
35+
F 6 Describe P ""
36+
F 9 Execute "" 0
37+
F 34 Parse "" "SELECT repeat('xyzxz', 12)" 0
38+
F 14 Bind "" "" 0 0 1 0
39+
F 6 Describe P ""
40+
F 9 Execute "" 0
41+
F 4 Flush
42+
B 4 ParseComplete
43+
B 4 BindComplete
44+
B 31 RowDescription 1 "repeat" NNNN 0 NNNN 65535 -1 0
45+
B 70 DataRow 1 60 'xyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxz'
46+
B 13 CommandComplete "SELECT 1"
47+
B 4 ParseComplete
48+
B 4 BindComplete
49+
B 31 RowDescription 1 "repeat" NNNN 0 NNNN 65535 -1 0
50+
B 70 DataRow 1 60 'xyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxz'
51+
B 13 CommandComplete "SELECT 1"
52+
B 4 ParseComplete
53+
B 4 BindComplete
54+
B 31 RowDescription 1 "repeat" NNNN 0 NNNN 65535 -1 0
55+
B 70 DataRow 1 60 'xyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxz'
56+
B 13 CommandComplete "SELECT 1"
57+
B 4 ParseComplete
58+
B 4 BindComplete
59+
B 31 RowDescription 1 "repeat" NNNN 0 NNNN 65535 -1 0
60+
B 70 DataRow 1 60 'xyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxz'
61+
B 13 CommandComplete "SELECT 1"
62+
B 4 ParseComplete
63+
B 4 BindComplete
64+
B 31 RowDescription 1 "repeat" NNNN 0 NNNN 65535 -1 0
65+
B 70 DataRow 1 60 'xyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxz'
66+
B 13 CommandComplete "SELECT 1"
67+
B 4 ParseComplete
68+
B 4 BindComplete
69+
B 31 RowDescription 1 "repeat" NNNN 0 NNNN 65535 -1 0
70+
B 70 DataRow 1 60 'xyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxz'
71+
B 13 CommandComplete "SELECT 1"
72+
B 4 ParseComplete
73+
B 4 BindComplete
74+
B 31 RowDescription 1 "repeat" NNNN 0 NNNN 65535 -1 0
75+
B 70 DataRow 1 60 'xyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxz'
76+
B 13 CommandComplete "SELECT 1"
77+
B 4 ParseComplete
78+
B 4 BindComplete
79+
B 31 RowDescription 1 "repeat" NNNN 0 NNNN 65535 -1 0
80+
B 70 DataRow 1 60 'xyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxz'
81+
B 13 CommandComplete "SELECT 1"
82+
B 4 ParseComplete
83+
B 4 BindComplete
84+
B 31 RowDescription 1 "repeat" NNNN 0 NNNN 65535 -1 0
85+
B 70 DataRow 1 60 'xyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxz'
86+
B 13 CommandComplete "SELECT 1"
87+
B 4 ParseComplete
88+
B 4 BindComplete
89+
B 31 RowDescription 1 "repeat" NNNN 0 NNNN 65535 -1 0
90+
B 70 DataRow 1 60 'xyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxzxyzxz'
91+
B 13 CommandComplete "SELECT 1"
92+
F 4 Terminate

0 commit comments

Comments
 (0)