Skip to content

Commit 2466d66

Browse files
committed
libpq-be-fe-helpers.h: wrap new cancel APIs
Commit 61461a3 introduced new functions to libpq for cancelling queries. This commit introduces a helper function that backend-side libraries and extensions can use to invoke those. This function takes a timeout and can itself be interrupted while it is waiting for a cancel request to be sent and processed, instead of being blocked. This replaces the usage of the old functions in postgres_fdw and dblink. Finally, it also adds some test coverage for the cancel support in postgres_fdw. Author: Jelte Fennema-Nio <postgres@jeltef.nl> Discussion: https://postgr.es/m/CAGECzQT_VgOWWENUqvUV9xQmbaCyXjtRRAYO8W07oqashk_N+g@mail.gmail.com
1 parent 4270057 commit 2466d66

File tree

5 files changed

+140
-39
lines changed

5 files changed

+140
-39
lines changed

contrib/dblink/dblink.c

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,25 +1347,16 @@ Datum
13471347
dblink_cancel_query(PG_FUNCTION_ARGS)
13481348
{
13491349
PGconn *conn;
1350-
PGcancelConn *cancelConn;
13511350
char *msg;
1351+
TimestampTz endtime;
13521352

13531353
dblink_init();
13541354
conn = dblink_get_named_conn(text_to_cstring(PG_GETARG_TEXT_PP(0)));
1355-
cancelConn = PQcancelCreate(conn);
1356-
1357-
PG_TRY();
1358-
{
1359-
if (!PQcancelBlocking(cancelConn))
1360-
msg = pchomp(PQcancelErrorMessage(cancelConn));
1361-
else
1362-
msg = "OK";
1363-
}
1364-
PG_FINALLY();
1365-
{
1366-
PQcancelFinish(cancelConn);
1367-
}
1368-
PG_END_TRY();
1355+
endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(),
1356+
30000);
1357+
msg = libpqsrv_cancel(conn, endtime);
1358+
if (msg == NULL)
1359+
msg = "OK";
13691360

13701361
PG_RETURN_TEXT_P(cstring_to_text(msg));
13711362
}

contrib/postgres_fdw/connection.c

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ static void pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue);
133133
static void pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry);
134134
static void pgfdw_reset_xact_state(ConnCacheEntry *entry, bool toplevel);
135135
static bool pgfdw_cancel_query(PGconn *conn);
136-
static bool pgfdw_cancel_query_begin(PGconn *conn);
136+
static bool pgfdw_cancel_query_begin(PGconn *conn, TimestampTz endtime);
137137
static bool pgfdw_cancel_query_end(PGconn *conn, TimestampTz endtime,
138138
bool consume_input);
139139
static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query,
@@ -1315,36 +1315,31 @@ pgfdw_cancel_query(PGconn *conn)
13151315
endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(),
13161316
CONNECTION_CLEANUP_TIMEOUT);
13171317

1318-
if (!pgfdw_cancel_query_begin(conn))
1318+
if (!pgfdw_cancel_query_begin(conn, endtime))
13191319
return false;
13201320
return pgfdw_cancel_query_end(conn, endtime, false);
13211321
}
13221322

1323+
/*
1324+
* Submit a cancel request to the given connection, waiting only until
1325+
* the given time.
1326+
*
1327+
* We sleep interruptibly until we receive confirmation that the cancel
1328+
* request has been accepted, and if it is, return true; if the timeout
1329+
* lapses without that, or the request fails for whatever reason, return
1330+
* false.
1331+
*/
13231332
static bool
1324-
pgfdw_cancel_query_begin(PGconn *conn)
1333+
pgfdw_cancel_query_begin(PGconn *conn, TimestampTz endtime)
13251334
{
1326-
PGcancel *cancel;
1327-
char errbuf[256];
1335+
char *errormsg = libpqsrv_cancel(conn, endtime);
13281336

1329-
/*
1330-
* Issue cancel request. Unfortunately, there's no good way to limit the
1331-
* amount of time that we might block inside PQgetCancel().
1332-
*/
1333-
if ((cancel = PQgetCancel(conn)))
1334-
{
1335-
if (!PQcancel(cancel, errbuf, sizeof(errbuf)))
1336-
{
1337-
ereport(WARNING,
1338-
(errcode(ERRCODE_CONNECTION_FAILURE),
1339-
errmsg("could not send cancel request: %s",
1340-
errbuf)));
1341-
PQfreeCancel(cancel);
1342-
return false;
1343-
}
1344-
PQfreeCancel(cancel);
1345-
}
1337+
if (errormsg != NULL)
1338+
ereport(WARNING,
1339+
errcode(ERRCODE_CONNECTION_FAILURE),
1340+
errmsg("could not send cancel request: %s", errormsg));
13461341

1347-
return true;
1342+
return errormsg == NULL;
13481343
}
13491344

13501345
static bool
@@ -1685,7 +1680,11 @@ pgfdw_abort_cleanup_begin(ConnCacheEntry *entry, bool toplevel,
16851680
*/
16861681
if (PQtransactionStatus(entry->conn) == PQTRANS_ACTIVE)
16871682
{
1688-
if (!pgfdw_cancel_query_begin(entry->conn))
1683+
TimestampTz endtime;
1684+
1685+
endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(),
1686+
CONNECTION_CLEANUP_TIMEOUT);
1687+
if (!pgfdw_cancel_query_begin(entry->conn, endtime))
16891688
return false; /* Unable to cancel running query */
16901689
*cancel_requested = lappend(*cancel_requested, entry);
16911690
}

contrib/postgres_fdw/expected/postgres_fdw.out

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2739,6 +2739,21 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
27392739
(10 rows)
27402740

27412741
ALTER VIEW v4 OWNER TO regress_view_owner;
2742+
-- Make sure this big CROSS JOIN query is pushed down
2743+
EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 CROSS JOIN ft2 CROSS JOIN ft4 CROSS JOIN ft5;
2744+
QUERY PLAN
2745+
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
2746+
Foreign Scan
2747+
Output: (count(*))
2748+
Relations: Aggregate on ((((public.ft1) INNER JOIN (public.ft2)) INNER JOIN (public.ft4)) INNER JOIN (public.ft5))
2749+
Remote SQL: SELECT count(*) FROM ((("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) INNER JOIN "S 1"."T 3" r4 ON (TRUE)) INNER JOIN "S 1"."T 4" r6 ON (TRUE))
2750+
(4 rows)
2751+
2752+
-- Make sure query cancellation works
2753+
SET statement_timeout = '10ms';
2754+
select count(*) from ft1 CROSS JOIN ft2 CROSS JOIN ft4 CROSS JOIN ft5; -- this takes very long
2755+
ERROR: canceling statement due to statement timeout
2756+
RESET statement_timeout;
27422757
-- ====================================================================
27432758
-- Check that userid to use when querying the remote table is correctly
27442759
-- propagated into foreign rels present in subqueries under an UNION ALL

contrib/postgres_fdw/sql/postgres_fdw.sql

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,13 @@ SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c
737737
SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
738738
ALTER VIEW v4 OWNER TO regress_view_owner;
739739

740+
-- Make sure this big CROSS JOIN query is pushed down
741+
EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 CROSS JOIN ft2 CROSS JOIN ft4 CROSS JOIN ft5;
742+
-- Make sure query cancellation works
743+
SET statement_timeout = '10ms';
744+
select count(*) from ft1 CROSS JOIN ft2 CROSS JOIN ft4 CROSS JOIN ft5; -- this takes very long
745+
RESET statement_timeout;
746+
740747
-- ====================================================================
741748
-- Check that userid to use when querying the remote table is correctly
742749
-- propagated into foreign rels present in subqueries under an UNION ALL

src/include/libpq/libpq-be-fe-helpers.h

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
#include "miscadmin.h"
4545
#include "storage/fd.h"
4646
#include "storage/latch.h"
47+
#include "utils/timestamp.h"
48+
#include "utils/wait_event.h"
4749

4850

4951
static inline void libpqsrv_connect_prepare(void);
@@ -365,4 +367,91 @@ libpqsrv_get_result(PGconn *conn, uint32 wait_event_info)
365367
return PQgetResult(conn);
366368
}
367369

370+
/*
371+
* Submit a cancel request to the given connection, waiting only until
372+
* the given time.
373+
*
374+
* We sleep interruptibly until we receive confirmation that the cancel
375+
* request has been accepted, and if it is, return NULL; if the cancel
376+
* request fails, return an error message string (which is not to be
377+
* freed).
378+
*
379+
* For other problems (to wit: OOM when strdup'ing an error message from
380+
* libpq), this function can ereport(ERROR).
381+
*
382+
* Note: this function leaks a string's worth of memory when reporting
383+
* libpq errors. Make sure to call it in a transient memory context.
384+
*/
385+
static inline char *
386+
libpqsrv_cancel(PGconn *conn, TimestampTz endtime)
387+
{
388+
PGcancelConn *cancel_conn;
389+
char *error = NULL;
390+
391+
cancel_conn = PQcancelCreate(conn);
392+
if (cancel_conn == NULL)
393+
return _("out of memory");
394+
395+
/* In what follows, do not leak any PGcancelConn on any errors. */
396+
397+
PG_TRY();
398+
{
399+
if (!PQcancelStart(cancel_conn))
400+
{
401+
error = pchomp(PQcancelErrorMessage(cancel_conn));
402+
goto exit;
403+
}
404+
405+
for (;;)
406+
{
407+
PostgresPollingStatusType pollres;
408+
TimestampTz now;
409+
long cur_timeout;
410+
int waitEvents = WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
411+
412+
pollres = PQcancelPoll(cancel_conn);
413+
if (pollres == PGRES_POLLING_OK)
414+
break; /* success! */
415+
416+
/* If timeout has expired, give up, else get sleep time. */
417+
now = GetCurrentTimestamp();
418+
cur_timeout = TimestampDifferenceMilliseconds(now, endtime);
419+
if (cur_timeout <= 0)
420+
{
421+
error = _("cancel request timed out");
422+
break;
423+
}
424+
425+
switch (pollres)
426+
{
427+
case PGRES_POLLING_READING:
428+
waitEvents |= WL_SOCKET_READABLE;
429+
break;
430+
case PGRES_POLLING_WRITING:
431+
waitEvents |= WL_SOCKET_WRITEABLE;
432+
break;
433+
default:
434+
error = pchomp(PQcancelErrorMessage(cancel_conn));
435+
goto exit;
436+
}
437+
438+
/* Sleep until there's something to do */
439+
WaitLatchOrSocket(MyLatch, waitEvents, PQcancelSocket(cancel_conn),
440+
cur_timeout, PG_WAIT_CLIENT);
441+
442+
ResetLatch(MyLatch);
443+
444+
CHECK_FOR_INTERRUPTS();
445+
}
446+
exit: ;
447+
}
448+
PG_FINALLY();
449+
{
450+
PQcancelFinish(cancel_conn);
451+
}
452+
PG_END_TRY();
453+
454+
return error;
455+
}
456+
368457
#endif /* LIBPQ_BE_FE_HELPERS_H */

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy