1
0
mirror of https://github.com/postgres/postgres.git synced 2025-10-24 01:29:19 +03:00
Files
postgres/src/interfaces/libpq/fe-cancel.c
Heikki Linnakangas a4801eb691 libpq: Complain about missing BackendKeyData later with PGgetCancel()
PostgreSQL always sends the BackendKeyData message at connection
startup, but there are some third party backend implementations out
there that don't support cancellation, and don't send the message
[1]. While the protocol docs left it up for interpretation if that is
valid behavior, libpq in PostgreSQL 17 and below accepted it. It does
not seem like the libpq behavior was intentional though, since it did
so by sending CancelRequest messages with all zeros to such servers
(instead of returning an error or making the cancel a no-op).

In version 18 the behavior was changed to return an error when trying
to create the cancel object with PGgetCancel() or PGcancelCreate().
This was done without any discussion, as part of supporting different
lengths of cancel packets for the new 3.2 version of the protocol.

This commit changes the behavior of PGgetCancel() / PGcancel() once
more to only return an error when the cancel object is actually used
to send a cancellation, instead of when merely creating the object.
The reason to do so is that some clients [2] create a cancel object as
part of their connection creation logic (thus having the cancel object
ready for later when they need it), so if creating the cancel object
returns an error, the whole connection attempt fails. By delaying the
error, such clients will still be able to connect to the third party
backend implementations in question, but when actually trying to
cancel a query, the user will be notified that that is not possible
for the server that they are connected to.

This commit only changes the behavior of the older PGgetCancel() /
PQcancel() functions, not the more modern PQcancelCreate() family of
functions.  I.e. PQcancelCreate() returns a failed connection object
(CONNECTION_BAD) if the server didn't send a cancellation key. Unlike
the old PQgetCancel() function, we're not aware of any clients in the
field that use PQcancelCreate() during connection startup in a way
that would prevent connecting to such servers.

[1] AWS RDS Proxy is definitely one of them, and CockroachDB might be
another.

[2] psycopg2 (but not psycopg3).

Author: Jelte Fennema-Nio <postgres@jeltef.nl>
Reviewed-by: Jacob Champion <jacob.champion@enterprisedb.com>
Backpatch-through: 18
Discussion: https://www.postgresql.org/message-id/20250617.101056.1437027795118961504.ishii%40postgresql.org
2025-08-01 18:24:19 +03:00

794 lines
21 KiB
C

/*-------------------------------------------------------------------------
*
* fe-cancel.c
* functions related to query cancellation
*
* Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/interfaces/libpq/fe-cancel.c
*
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include <unistd.h>
#include "libpq-fe.h"
#include "libpq-int.h"
#include "port/pg_bswap.h"
/*
* pg_cancel_conn (backing struct for PGcancelConn) is a wrapper around a
* PGconn to send cancellations using PQcancelBlocking and PQcancelStart.
* This isn't just a typedef because we want the compiler to complain when a
* PGconn is passed to a function that expects a PGcancelConn, and vice versa.
*/
struct pg_cancel_conn
{
PGconn conn;
};
/*
* pg_cancel (backing struct for PGcancel) stores all data necessary to send a
* cancel request.
*/
struct pg_cancel
{
SockAddr raddr; /* Remote address */
int be_pid; /* PID of to-be-canceled backend */
int pgtcp_user_timeout; /* tcp user timeout */
int keepalives; /* use TCP keepalives? */
int keepalives_idle; /* time between TCP keepalives */
int keepalives_interval; /* time between TCP keepalive
* retransmits */
int keepalives_count; /* maximum number of TCP keepalive
* retransmits */
/* Pre-constructed cancel request packet starts here */
int32 cancel_pkt_len; /* in network byte order */
char cancel_req[FLEXIBLE_ARRAY_MEMBER]; /* CancelRequestPacket */
};
/*
* PQcancelCreate
*
* Create and return a PGcancelConn, which can be used to securely cancel a
* query on the given connection.
*
* This requires either following the non-blocking flow through
* PQcancelStart() and PQcancelPoll(), or the blocking PQcancelBlocking().
*/
PGcancelConn *
PQcancelCreate(PGconn *conn)
{
PGconn *cancelConn = pqMakeEmptyPGconn();
pg_conn_host originalHost;
if (cancelConn == NULL)
return NULL;
/* Check we have an open connection */
if (!conn)
{
libpq_append_conn_error(cancelConn, "connection pointer is NULL");
return (PGcancelConn *) cancelConn;
}
if (conn->sock == PGINVALID_SOCKET)
{
libpq_append_conn_error(cancelConn, "connection not open");
return (PGcancelConn *) cancelConn;
}
/* Check that we have received a cancellation key */
if (conn->be_cancel_key_len == 0)
{
libpq_append_conn_error(cancelConn, "no cancellation key received");
return (PGcancelConn *) cancelConn;
}
/*
* Indicate that this connection is used to send a cancellation
*/
cancelConn->cancelRequest = true;
if (!pqCopyPGconn(conn, cancelConn))
return (PGcancelConn *) cancelConn;
/*
* Compute derived options
*/
if (!pqConnectOptions2(cancelConn))
return (PGcancelConn *) cancelConn;
/*
* Copy cancellation token data from the original connection
*/
cancelConn->be_pid = conn->be_pid;
if (conn->be_cancel_key != NULL)
{
cancelConn->be_cancel_key = malloc(conn->be_cancel_key_len);
if (cancelConn->be_cancel_key == NULL)
goto oom_error;
memcpy(cancelConn->be_cancel_key, conn->be_cancel_key, conn->be_cancel_key_len);
}
cancelConn->be_cancel_key_len = conn->be_cancel_key_len;
cancelConn->pversion = conn->pversion;
/*
* Cancel requests should not iterate over all possible hosts. The request
* needs to be sent to the exact host and address that the original
* connection used. So we manually create the host and address arrays with
* a single element after freeing the host array that we generated from
* the connection options.
*/
pqReleaseConnHosts(cancelConn);
cancelConn->nconnhost = 1;
cancelConn->naddr = 1;
cancelConn->connhost = calloc(cancelConn->nconnhost, sizeof(pg_conn_host));
if (!cancelConn->connhost)
goto oom_error;
originalHost = conn->connhost[conn->whichhost];
cancelConn->connhost[0].type = originalHost.type;
if (originalHost.host)
{
cancelConn->connhost[0].host = strdup(originalHost.host);
if (!cancelConn->connhost[0].host)
goto oom_error;
}
if (originalHost.hostaddr)
{
cancelConn->connhost[0].hostaddr = strdup(originalHost.hostaddr);
if (!cancelConn->connhost[0].hostaddr)
goto oom_error;
}
if (originalHost.port)
{
cancelConn->connhost[0].port = strdup(originalHost.port);
if (!cancelConn->connhost[0].port)
goto oom_error;
}
if (originalHost.password)
{
cancelConn->connhost[0].password = strdup(originalHost.password);
if (!cancelConn->connhost[0].password)
goto oom_error;
}
cancelConn->addr = calloc(cancelConn->naddr, sizeof(AddrInfo));
if (!cancelConn->addr)
goto oom_error;
cancelConn->addr[0].addr = conn->raddr;
cancelConn->addr[0].family = conn->raddr.addr.ss_family;
cancelConn->status = CONNECTION_ALLOCATED;
return (PGcancelConn *) cancelConn;
oom_error:
cancelConn->status = CONNECTION_BAD;
libpq_append_conn_error(cancelConn, "out of memory");
return (PGcancelConn *) cancelConn;
}
/*
* PQcancelBlocking
*
* Send a cancellation request in a blocking fashion.
* Returns 1 if successful 0 if not.
*/
int
PQcancelBlocking(PGcancelConn *cancelConn)
{
if (!PQcancelStart(cancelConn))
return 0;
return pqConnectDBComplete(&cancelConn->conn);
}
/*
* PQcancelStart
*
* Starts sending a cancellation request in a non-blocking fashion. Returns
* 1 if successful 0 if not.
*/
int
PQcancelStart(PGcancelConn *cancelConn)
{
if (!cancelConn || cancelConn->conn.status == CONNECTION_BAD)
return 0;
if (cancelConn->conn.status != CONNECTION_ALLOCATED)
{
libpq_append_conn_error(&cancelConn->conn,
"cancel request is already being sent on this connection");
cancelConn->conn.status = CONNECTION_BAD;
return 0;
}
return pqConnectDBStart(&cancelConn->conn);
}
/*
* PQcancelPoll
*
* Poll a cancel connection. For usage details see PQconnectPoll.
*/
PostgresPollingStatusType
PQcancelPoll(PGcancelConn *cancelConn)
{
PGconn *conn = &cancelConn->conn;
int n;
/*
* We leave most of the connection establishment to PQconnectPoll, since
* it's very similar to normal connection establishment. But once we get
* to the CONNECTION_AWAITING_RESPONSE we need to start doing our own
* thing.
*/
if (conn->status != CONNECTION_AWAITING_RESPONSE)
{
return PQconnectPoll(conn);
}
/*
* At this point we are waiting on the server to close the connection,
* which is its way of communicating that the cancel has been handled.
*/
n = pqReadData(conn);
if (n == 0)
return PGRES_POLLING_READING;
#ifndef WIN32
/*
* If we receive an error report it, but only if errno is non-zero.
* Otherwise we assume it's an EOF, which is what we expect from the
* server.
*
* We skip this for Windows, because Windows is a bit special in its EOF
* behaviour for TCP. Sometimes it will error with an ECONNRESET when
* there is a clean connection closure. See these threads for details:
* https://www.postgresql.org/message-id/flat/90b34057-4176-7bb0-0dbb-9822a5f6425b%40greiz-reinsdorf.de
*
* https://www.postgresql.org/message-id/flat/CA%2BhUKG%2BOeoETZQ%3DQw5Ub5h3tmwQhBmDA%3DnuNO3KG%3DzWfUypFAw%40mail.gmail.com
*
* PQcancel ignores such errors and reports success for the cancellation
* anyway, so even if this is not always correct we do the same here.
*/
if (n < 0 && errno != 0)
{
conn->status = CONNECTION_BAD;
return PGRES_POLLING_FAILED;
}
#endif
/*
* We don't expect any data, only connection closure. So if we strangely
* do receive some data we consider that an error.
*/
if (n > 0)
{
libpq_append_conn_error(conn, "unexpected response from server");
conn->status = CONNECTION_BAD;
return PGRES_POLLING_FAILED;
}
/*
* Getting here means that we received an EOF, which is what we were
* expecting -- the cancel request has completed.
*/
cancelConn->conn.status = CONNECTION_OK;
resetPQExpBuffer(&conn->errorMessage);
return PGRES_POLLING_OK;
}
/*
* PQcancelStatus
*
* Get the status of a cancel connection.
*/
ConnStatusType
PQcancelStatus(const PGcancelConn *cancelConn)
{
return PQstatus(&cancelConn->conn);
}
/*
* PQcancelSocket
*
* Get the socket of the cancel connection.
*/
int
PQcancelSocket(const PGcancelConn *cancelConn)
{
return PQsocket(&cancelConn->conn);
}
/*
* PQcancelErrorMessage
*
* Returns the error message most recently generated by an operation on the
* cancel connection.
*/
char *
PQcancelErrorMessage(const PGcancelConn *cancelConn)
{
return PQerrorMessage(&cancelConn->conn);
}
/*
* PQcancelReset
*
* Resets the cancel connection, so it can be reused to send a new cancel
* request.
*/
void
PQcancelReset(PGcancelConn *cancelConn)
{
pqClosePGconn(&cancelConn->conn);
cancelConn->conn.status = CONNECTION_ALLOCATED;
cancelConn->conn.whichhost = 0;
cancelConn->conn.whichaddr = 0;
cancelConn->conn.try_next_host = false;
cancelConn->conn.try_next_addr = false;
}
/*
* PQcancelFinish
*
* Closes and frees the cancel connection.
*/
void
PQcancelFinish(PGcancelConn *cancelConn)
{
PQfinish(&cancelConn->conn);
}
/*
* PQgetCancel: get a PGcancel structure corresponding to a connection.
*
* A copy is needed to be able to cancel a running query from a different
* thread. If the same structure is used all structure members would have
* to be individually locked (if the entire structure was locked, it would
* be impossible to cancel a synchronous query because the structure would
* have to stay locked for the duration of the query).
*/
PGcancel *
PQgetCancel(PGconn *conn)
{
PGcancel *cancel;
int cancel_req_len;
CancelRequestPacket *req;
if (!conn)
return NULL;
if (conn->sock == PGINVALID_SOCKET)
return NULL;
/* Check that we have received a cancellation key */
if (conn->be_cancel_key_len == 0)
{
/*
* In case there is no cancel key, return an all-zero PGcancel object.
* Actually calling PQcancel on this will fail, but we allow creating
* the PGcancel object anyway. Arguably it would be better return NULL
* to indicate that cancellation is not possible, but there'd be no
* way for the caller to distinguish "out of memory" from "server did
* not send a cancel key". Also, this is how PGgetCancel() has always
* behaved, and if we changed it, some clients would stop working
* altogether with servers that don't support cancellation. (The
* modern PQcancelCreate() function returns a failed connection object
* instead.)
*
* The returned dummy object has cancel_pkt_len == 0; we check for
* that in PQcancel() to identify it as a dummy.
*/
return calloc(1, sizeof(PGcancel));
}
cancel_req_len = offsetof(CancelRequestPacket, cancelAuthCode) + conn->be_cancel_key_len;
cancel = malloc(offsetof(PGcancel, cancel_req) + cancel_req_len);
if (cancel == NULL)
return NULL;
memcpy(&cancel->raddr, &conn->raddr, sizeof(SockAddr));
/* We use -1 to indicate an unset connection option */
cancel->pgtcp_user_timeout = -1;
cancel->keepalives = -1;
cancel->keepalives_idle = -1;
cancel->keepalives_interval = -1;
cancel->keepalives_count = -1;
if (conn->pgtcp_user_timeout != NULL)
{
if (!pqParseIntParam(conn->pgtcp_user_timeout,
&cancel->pgtcp_user_timeout,
conn, "tcp_user_timeout"))
goto fail;
}
if (conn->keepalives != NULL)
{
if (!pqParseIntParam(conn->keepalives,
&cancel->keepalives,
conn, "keepalives"))
goto fail;
}
if (conn->keepalives_idle != NULL)
{
if (!pqParseIntParam(conn->keepalives_idle,
&cancel->keepalives_idle,
conn, "keepalives_idle"))
goto fail;
}
if (conn->keepalives_interval != NULL)
{
if (!pqParseIntParam(conn->keepalives_interval,
&cancel->keepalives_interval,
conn, "keepalives_interval"))
goto fail;
}
if (conn->keepalives_count != NULL)
{
if (!pqParseIntParam(conn->keepalives_count,
&cancel->keepalives_count,
conn, "keepalives_count"))
goto fail;
}
req = (CancelRequestPacket *) &cancel->cancel_req;
req->cancelRequestCode = (MsgType) pg_hton32(CANCEL_REQUEST_CODE);
req->backendPID = pg_hton32(conn->be_pid);
memcpy(req->cancelAuthCode, conn->be_cancel_key, conn->be_cancel_key_len);
/* include the length field itself in the length */
cancel->cancel_pkt_len = pg_hton32(cancel_req_len + 4);
return cancel;
fail:
free(cancel);
return NULL;
}
/*
* PQsendCancelRequest
* Submit a CancelRequest message, but don't wait for it to finish
*
* Returns: 1 if successfully submitted
* 0 if error (conn->errorMessage is set)
*/
int
PQsendCancelRequest(PGconn *cancelConn)
{
CancelRequestPacket req;
/* Start the message. */
if (pqPutMsgStart(0, cancelConn))
return STATUS_ERROR;
/* Send the message body. */
memset(&req, 0, offsetof(CancelRequestPacket, cancelAuthCode));
req.cancelRequestCode = (MsgType) pg_hton32(CANCEL_REQUEST_CODE);
req.backendPID = pg_hton32(cancelConn->be_pid);
if (pqPutnchar(&req, offsetof(CancelRequestPacket, cancelAuthCode), cancelConn))
return STATUS_ERROR;
if (pqPutnchar(cancelConn->be_cancel_key, cancelConn->be_cancel_key_len, cancelConn))
return STATUS_ERROR;
/* Finish the message. */
if (pqPutMsgEnd(cancelConn))
return STATUS_ERROR;
/* Flush to ensure backend gets it. */
if (pqFlush(cancelConn))
return STATUS_ERROR;
return STATUS_OK;
}
/* PQfreeCancel: free a cancel structure */
void
PQfreeCancel(PGcancel *cancel)
{
free(cancel);
}
/*
* Sets an integer socket option on a TCP socket, if the provided value is
* not negative. Returns false if setsockopt fails for some reason.
*
* CAUTION: This needs to be signal safe, since it's used by PQcancel.
*/
#if defined(TCP_USER_TIMEOUT) || !defined(WIN32)
static bool
optional_setsockopt(int fd, int protoid, int optid, int value)
{
if (value < 0)
return true;
if (setsockopt(fd, protoid, optid, (char *) &value, sizeof(value)) < 0)
return false;
return true;
}
#endif
/*
* PQcancel: old, non-encrypted, but signal-safe way of requesting query cancel
*
* The return value is true if the cancel request was successfully
* dispatched, false if not (in which case an error message is available).
* Note: successful dispatch is no guarantee that there will be any effect at
* the backend. The application must read the operation result as usual.
*
* On failure, an error message is stored in *errbuf, which must be of size
* errbufsize (recommended size is 256 bytes). *errbuf is not changed on
* success return.
*
* CAUTION: we want this routine to be safely callable from a signal handler
* (for example, an application might want to call it in a SIGINT handler).
* This means we cannot use any C library routine that might be non-reentrant.
* malloc/free are often non-reentrant, and anything that might call them is
* just as dangerous. We avoid sprintf here for that reason. Building up
* error messages with strcpy/strcat is tedious but should be quite safe.
* We also save/restore errno in case the signal handler support doesn't.
*/
int
PQcancel(PGcancel *cancel, char *errbuf, int errbufsize)
{
int save_errno = SOCK_ERRNO;
pgsocket tmpsock = PGINVALID_SOCKET;
int maxlen;
char recvbuf;
int cancel_pkt_len;
if (!cancel)
{
strlcpy(errbuf, "PQcancel() -- no cancel object supplied", errbufsize);
/* strlcpy probably doesn't change errno, but be paranoid */
SOCK_ERRNO_SET(save_errno);
return false;
}
if (cancel->cancel_pkt_len == 0)
{
/* This is a dummy PGcancel object, see PQgetCancel */
strlcpy(errbuf, "PQcancel() -- no cancellation key received", errbufsize);
/* strlcpy probably doesn't change errno, but be paranoid */
SOCK_ERRNO_SET(save_errno);
return false;
}
/*
* We need to open a temporary connection to the postmaster. Do this with
* only kernel calls.
*/
if ((tmpsock = socket(cancel->raddr.addr.ss_family, SOCK_STREAM, 0)) == PGINVALID_SOCKET)
{
strlcpy(errbuf, "PQcancel() -- socket() failed: ", errbufsize);
goto cancel_errReturn;
}
/*
* Since this connection will only be used to send a single packet of
* data, we don't need NODELAY. We also don't set the socket to
* nonblocking mode, because the API definition of PQcancel requires the
* cancel to be sent in a blocking way.
*
* We do set socket options related to keepalives and other TCP timeouts.
* This ensures that this function does not block indefinitely when
* reasonable keepalive and timeout settings have been provided.
*/
if (cancel->raddr.addr.ss_family != AF_UNIX &&
cancel->keepalives != 0)
{
#ifndef WIN32
if (!optional_setsockopt(tmpsock, SOL_SOCKET, SO_KEEPALIVE, 1))
{
strlcpy(errbuf, "PQcancel() -- setsockopt(SO_KEEPALIVE) failed: ", errbufsize);
goto cancel_errReturn;
}
#ifdef PG_TCP_KEEPALIVE_IDLE
if (!optional_setsockopt(tmpsock, IPPROTO_TCP, PG_TCP_KEEPALIVE_IDLE,
cancel->keepalives_idle))
{
strlcpy(errbuf, "PQcancel() -- setsockopt(" PG_TCP_KEEPALIVE_IDLE_STR ") failed: ", errbufsize);
goto cancel_errReturn;
}
#endif
#ifdef TCP_KEEPINTVL
if (!optional_setsockopt(tmpsock, IPPROTO_TCP, TCP_KEEPINTVL,
cancel->keepalives_interval))
{
strlcpy(errbuf, "PQcancel() -- setsockopt(TCP_KEEPINTVL) failed: ", errbufsize);
goto cancel_errReturn;
}
#endif
#ifdef TCP_KEEPCNT
if (!optional_setsockopt(tmpsock, IPPROTO_TCP, TCP_KEEPCNT,
cancel->keepalives_count))
{
strlcpy(errbuf, "PQcancel() -- setsockopt(TCP_KEEPCNT) failed: ", errbufsize);
goto cancel_errReturn;
}
#endif
#else /* WIN32 */
#ifdef SIO_KEEPALIVE_VALS
if (!pqSetKeepalivesWin32(tmpsock,
cancel->keepalives_idle,
cancel->keepalives_interval))
{
strlcpy(errbuf, "PQcancel() -- WSAIoctl(SIO_KEEPALIVE_VALS) failed: ", errbufsize);
goto cancel_errReturn;
}
#endif /* SIO_KEEPALIVE_VALS */
#endif /* WIN32 */
/* TCP_USER_TIMEOUT works the same way on Unix and Windows */
#ifdef TCP_USER_TIMEOUT
if (!optional_setsockopt(tmpsock, IPPROTO_TCP, TCP_USER_TIMEOUT,
cancel->pgtcp_user_timeout))
{
strlcpy(errbuf, "PQcancel() -- setsockopt(TCP_USER_TIMEOUT) failed: ", errbufsize);
goto cancel_errReturn;
}
#endif
}
retry3:
if (connect(tmpsock, (struct sockaddr *) &cancel->raddr.addr,
cancel->raddr.salen) < 0)
{
if (SOCK_ERRNO == EINTR)
/* Interrupted system call - we'll just try again */
goto retry3;
strlcpy(errbuf, "PQcancel() -- connect() failed: ", errbufsize);
goto cancel_errReturn;
}
cancel_pkt_len = pg_ntoh32(cancel->cancel_pkt_len);
retry4:
/*
* Send the cancel request packet. It starts with the message length at
* cancel_pkt_len, followed by the actual packet.
*/
if (send(tmpsock, (char *) &cancel->cancel_pkt_len, cancel_pkt_len, 0) != cancel_pkt_len)
{
if (SOCK_ERRNO == EINTR)
/* Interrupted system call - we'll just try again */
goto retry4;
strlcpy(errbuf, "PQcancel() -- send() failed: ", errbufsize);
goto cancel_errReturn;
}
/*
* Wait for the postmaster to close the connection, which indicates that
* it's processed the request. Without this delay, we might issue another
* command only to find that our cancel zaps that command instead of the
* one we thought we were canceling. Note we don't actually expect this
* read to obtain any data, we are just waiting for EOF to be signaled.
*/
retry5:
if (recv(tmpsock, &recvbuf, 1, 0) < 0)
{
if (SOCK_ERRNO == EINTR)
/* Interrupted system call - we'll just try again */
goto retry5;
/* we ignore other error conditions */
}
/* All done */
closesocket(tmpsock);
SOCK_ERRNO_SET(save_errno);
return true;
cancel_errReturn:
/*
* Make sure we don't overflow the error buffer. Leave space for the \n at
* the end, and for the terminating zero.
*/
maxlen = errbufsize - strlen(errbuf) - 2;
if (maxlen >= 0)
{
/*
* We can't invoke strerror here, since it's not signal-safe. Settle
* for printing the decimal value of errno. Even that has to be done
* the hard way.
*/
int val = SOCK_ERRNO;
char buf[32];
char *bufp;
bufp = buf + sizeof(buf) - 1;
*bufp = '\0';
do
{
*(--bufp) = (val % 10) + '0';
val /= 10;
} while (val > 0);
bufp -= 6;
memcpy(bufp, "error ", 6);
strncat(errbuf, bufp, maxlen);
strcat(errbuf, "\n");
}
if (tmpsock != PGINVALID_SOCKET)
closesocket(tmpsock);
SOCK_ERRNO_SET(save_errno);
return false;
}
/*
* PQrequestCancel: old, not thread-safe function for requesting query cancel
*
* Returns true if able to send the cancel request, false if not.
*
* On failure, the error message is saved in conn->errorMessage; this means
* that this can't be used when there might be other active operations on
* the connection object.
*
* NOTE: error messages will be cut off at the current size of the
* error message buffer, since we dare not try to expand conn->errorMessage!
*/
int
PQrequestCancel(PGconn *conn)
{
int r;
PGcancel *cancel;
/* Check we have an open connection */
if (!conn)
return false;
if (conn->sock == PGINVALID_SOCKET)
{
strlcpy(conn->errorMessage.data,
"PQrequestCancel() -- connection is not open\n",
conn->errorMessage.maxlen);
conn->errorMessage.len = strlen(conn->errorMessage.data);
conn->errorReported = 0;
return false;
}
cancel = PQgetCancel(conn);
if (cancel)
{
r = PQcancel(cancel, conn->errorMessage.data,
conn->errorMessage.maxlen);
PQfreeCancel(cancel);
}
else
{
strlcpy(conn->errorMessage.data, "out of memory",
conn->errorMessage.maxlen);
r = false;
}
if (!r)
{
conn->errorMessage.len = strlen(conn->errorMessage.data);
conn->errorReported = 0;
}
return r;
}