Index: bin/psql/common.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/bin/psql/common.c,v retrieving revision 1.92 diff -c -r1.92 common.c *** bin/psql/common.c 10 Oct 2004 23:37:40 -0000 1.92 --- bin/psql/common.c 30 Oct 2004 19:45:45 -0000 *************** *** 223,245 **** * * Before we start a query, we enable a SIGINT signal catcher that sends a * cancel request to the backend. Note that sending the cancel directly from ! * the signal handler is safe because PQrequestCancel() is written to make it * so. We use write() to print to stdout because it's better to use simple * facilities in a signal handler. */ ! static PGconn *volatile cancelConn = NULL; volatile bool cancel_pressed = false; - #ifndef WIN32 #define write_stderr(String) write(fileno(stderr), String, strlen(String)) void handle_sigint(SIGNAL_ARGS) { int save_errno = errno; /* Don't muck around if prompting for a password. */ if (prompt_state) --- 223,254 ---- * * Before we start a query, we enable a SIGINT signal catcher that sends a * cancel request to the backend. Note that sending the cancel directly from ! * the signal handler is safe because PQcancel() is written to make it * so. We use write() to print to stdout because it's better to use simple * facilities in a signal handler. + * + * On win32, the signal cancelling happens on a separate thread, because + * that's how SetConsoleCtrlHandler works. The PQcancel function is safe + * or this, unlike PQrequestCancel. However, a CRITICAL_SECTION is required + * to protect the PGcancel structure during all usage. */ ! static PGcancel *cancelConn = NULL; ! #ifdef WIN32 ! static CRITICAL_SECTION cancelConnLock; ! #endif volatile bool cancel_pressed = false; #define write_stderr(String) write(fileno(stderr), String, strlen(String)) + #ifndef WIN32 void handle_sigint(SIGNAL_ARGS) { int save_errno = errno; + char errbuf[256]; /* Don't muck around if prompting for a password. */ if (prompt_state) *************** *** 250,265 **** cancel_pressed = true; ! if (PQrequestCancel(cancelConn)) write_stderr("Cancel request sent\n"); else { write_stderr("Could not send cancel request: "); ! write_stderr(PQerrorMessage(cancelConn)); } errno = save_errno; /* just in case the write changed it */ } ! #endif /* not WIN32 */ --- 259,315 ---- cancel_pressed = true; ! if (PQcancel(cancelConn, errbuf, sizeof(errbuf))) write_stderr("Cancel request sent\n"); else { write_stderr("Could not send cancel request: "); ! write_stderr(errbuf); } errno = save_errno; /* just in case the write changed it */ } ! #else /* WIN32 */ ! ! static BOOL WINAPI consoleHandler(DWORD dwCtrlType) ! { ! char errbuf[256]; ! ! if (dwCtrlType == CTRL_C_EVENT || ! dwCtrlType == CTRL_BREAK_EVENT) ! { ! if (prompt_state) ! return TRUE; ! ! /* Perform query cancel */ ! EnterCriticalSection(&cancelConnLock); ! if (cancelConn != NULL) ! { ! cancel_pressed = true; ! ! if (PQcancel(cancelConn, errbuf, sizeof(errbuf))) ! write_stderr("Cancel request sent\n"); ! else ! { ! write_stderr("Could not send cancel request: "); ! write_stderr(errbuf); ! } ! } ! LeaveCriticalSection(&cancelConnLock); ! ! return TRUE; ! } ! else ! /* Return FALSE for any signals not being handled */ ! return FALSE; ! } ! ! void ! setup_cancel_handler(void) ! { ! InitializeCriticalSection(&cancelConnLock); ! SetConsoleCtrlHandler(consoleHandler, TRUE); ! } ! #endif *************** *** 327,346 **** static void SetCancelConn(void) { ! cancelConn = pset.db; } /* * ResetCancelConn * ! * Set cancelConn to NULL. I don't know what this means exactly, but it saves ! * having to export the variable. */ void ResetCancelConn(void) { cancelConn = NULL; } --- 377,414 ---- static void SetCancelConn(void) { ! #ifdef WIN32 ! EnterCriticalSection(&cancelConnLock); ! #endif ! /* Free the old one if we have one */ ! if (cancelConn != NULL) ! PQfreeCancel(cancelConn); ! ! cancelConn = PQgetCancel(pset.db); ! #ifdef WIN32 ! LeaveCriticalSection(&cancelConnLock); ! #endif } /* * ResetCancelConn * ! * Free the current cancel connection, if any, and set to NULL. */ void ResetCancelConn(void) { + #ifdef WIN32 + EnterCriticalSection(&cancelConnLock); + #endif + if (cancelConn) + PQfreeCancel(cancelConn); + cancelConn = NULL; + #ifdef WIN32 + LeaveCriticalSection(&cancelConnLock); + #endif } Index: bin/psql/common.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/bin/psql/common.h,v retrieving revision 1.39 diff -c -r1.39 common.h *** bin/psql/common.h 29 Aug 2004 04:13:02 -0000 1.39 --- bin/psql/common.h 30 Oct 2004 12:34:40 -0000 *************** *** 48,54 **** #ifndef WIN32 extern void handle_sigint(SIGNAL_ARGS); ! #endif /* not WIN32 */ extern PGresult *PSQLexec(const char *query, bool start_xact); --- 48,56 ---- #ifndef WIN32 extern void handle_sigint(SIGNAL_ARGS); ! #else /* WIN32 */ ! extern void setup_cancel_handler(void); ! #endif extern PGresult *PSQLexec(const char *query, bool start_xact); Index: bin/psql/mainloop.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/bin/psql/mainloop.c,v retrieving revision 1.64 diff -c -r1.64 mainloop.c *** bin/psql/mainloop.c 29 Aug 2004 05:06:54 -0000 1.64 --- bin/psql/mainloop.c 30 Oct 2004 12:31:57 -0000 *************** *** 121,127 **** * ready */ pqsignal(SIGINT, handle_sigint); /* control-C => cancel */ ! #endif /* not WIN32 */ fflush(stdout); --- 121,129 ---- * ready */ pqsignal(SIGINT, handle_sigint); /* control-C => cancel */ ! #else /* WIN32 */ ! setup_cancel_handler(); /* in common.c */ ! #endif fflush(stdout); Index: interfaces/libpq/exports.txt =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/exports.txt,v retrieving revision 1.2 diff -c -r1.2 exports.txt *** interfaces/libpq/exports.txt 18 Oct 2004 22:00:42 -0000 1.2 --- interfaces/libpq/exports.txt 30 Oct 2004 19:39:18 -0000 *************** *** 119,121 **** --- 119,124 ---- pqsignal 117 PQprepare 118 PQsendPrepare 119 + PQgetCancel 120 + PQfreeCancel 121 + PQcancel 122 Index: interfaces/libpq/fe-connect.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v retrieving revision 1.288 diff -c -r1.288 fe-connect.c *** interfaces/libpq/fe-connect.c 29 Oct 2004 19:30:02 -0000 1.288 --- interfaces/libpq/fe-connect.c 30 Oct 2004 19:43:00 -0000 *************** *** 2189,2197 **** return PGRES_POLLING_FAILED; } /* ! * PQrequestCancel: attempt to request cancellation of the current operation. * * The return value is TRUE if the cancel request was successfully * dispatched, FALSE if not (in which case conn->errorMessage is set). --- 2189,2243 ---- return PGRES_POLLING_FAILED; } + /* + * PQcancelGet: 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 becuase the structure would + * have to stay locked for the duration of the query). + */ + PGcancel * + PQgetCancel(PGconn *conn) + { + PGcancel *cancel; + + if (!conn) + return NULL; + + if (conn->sock < 0) + return NULL; + + cancel = malloc(sizeof(PGcancel)); + if (cancel == NULL) + return NULL; + + memcpy(&cancel->raddr, &conn->raddr, sizeof(SockAddr)); + cancel->be_pid = conn->be_pid; + cancel->be_key = conn->be_key; + + return cancel; + } + + /* PQfreeCancel: free a cancel structure */ + void + PQfreeCancel(PGcancel *cancel) + { + if (!cancel) + return; + + free(cancel); + } /* ! * PQcancel and PQrequestCancel: attempt to request cancellation of the ! * current operation. ! * ! * PQrequestCancel is the backwards compatible version. This version is ! * safe to call from signal handlers, but it is NOT safe to call from ! * a different thread. PQcancel is safe both from a different thread and ! * from a signal handler. * * The return value is TRUE if the cancel request was successfully * dispatched, FALSE if not (in which case conn->errorMessage is set). *************** *** 2201,2206 **** --- 2247,2254 ---- * XXX it was a bad idea to have the error message returned in * conn->errorMessage, since it could overwrite a message already there. * Would be better to return it in a char array passed by the caller. + * This has been fixed in the PQcancel function, and only applies to + * PQrequestCancel. * * 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). *************** *** 2210,2268 **** * 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. * ! * NOTE: this routine must not generate any error message longer than ! * INITIAL_EXPBUFFER_SIZE (currently 256), since we dare not try to * expand conn->errorMessage! */ ! int ! PQrequestCancel(PGconn *conn) { int save_errno = SOCK_ERRNO; int tmpsock = -1; char sebuf[256]; struct { uint32 packetlen; CancelRequestPacket cp; } crp; - /* Check we have an open connection */ - if (!conn) - return FALSE; - - if (conn->sock < 0) - { - strcpy(conn->errorMessage.data, - "PQrequestCancel() -- connection is not open\n"); - conn->errorMessage.len = strlen(conn->errorMessage.data); - #ifdef WIN32 - WSASetLastError(save_errno); - #else - errno = save_errno; - #endif - return FALSE; - } - /* ! * We need to open a temporary connection to the postmaster. Use the ! * information saved by connectDB to do this with only kernel calls. */ ! if ((tmpsock = socket(conn->raddr.addr.ss_family, SOCK_STREAM, 0)) < 0) { ! strcpy(conn->errorMessage.data, ! "PQrequestCancel() -- socket() failed: "); goto cancel_errReturn; } retry3: ! if (connect(tmpsock, (struct sockaddr *) & conn->raddr.addr, ! conn->raddr.salen) < 0) { if (SOCK_ERRNO == EINTR) /* Interrupted system call - we'll just try again */ goto retry3; ! strcpy(conn->errorMessage.data, ! "PQrequestCancel() -- connect() failed: "); goto cancel_errReturn; } --- 2258,2303 ---- * 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. * ! * 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! */ ! /* internal_cancel() is an internal helper function to make code-sharing ! * between the two versions of the cancel function possible. ! */ ! static int ! internal_cancel(SockAddr *raddr, int be_pid, int be_key, char *errbuf, int errbufsize) { int save_errno = SOCK_ERRNO; int tmpsock = -1; char sebuf[256]; + int maxlen; struct { uint32 packetlen; CancelRequestPacket cp; } crp; /* ! * We need to open a temporary connection to the postmaster. Do ! * this with only kernel calls. */ ! if ((tmpsock = socket(raddr->addr.ss_family, SOCK_STREAM, 0)) < 0) { ! strncpy(errbuf, ! "PQrequestCancel() -- socket() failed: ", errbufsize); goto cancel_errReturn; } retry3: ! if (connect(tmpsock, (struct sockaddr *) & raddr->addr, ! raddr->salen) < 0) { if (SOCK_ERRNO == EINTR) /* Interrupted system call - we'll just try again */ goto retry3; ! strncpy(errbuf, ! "PQrequestCancel() -- connect() failed: ", errbufsize); goto cancel_errReturn; } *************** *** 2274,2281 **** crp.packetlen = htonl((uint32) sizeof(crp)); crp.cp.cancelRequestCode = (MsgType) htonl(CANCEL_REQUEST_CODE); ! crp.cp.backendPID = htonl(conn->be_pid); ! crp.cp.cancelAuthCode = htonl(conn->be_key); retry4: if (send(tmpsock, (char *) &crp, sizeof(crp), 0) != (int) sizeof(crp)) --- 2309,2316 ---- crp.packetlen = htonl((uint32) sizeof(crp)); crp.cp.cancelRequestCode = (MsgType) htonl(CANCEL_REQUEST_CODE); ! crp.cp.backendPID = htonl(be_pid); ! crp.cp.cancelAuthCode = htonl(be_key); retry4: if (send(tmpsock, (char *) &crp, sizeof(crp), 0) != (int) sizeof(crp)) *************** *** 2283,2290 **** if (SOCK_ERRNO == EINTR) /* Interrupted system call - we'll just try again */ goto retry4; ! strcpy(conn->errorMessage.data, ! "PQrequestCancel() -- send() failed: "); goto cancel_errReturn; } --- 2318,2325 ---- if (SOCK_ERRNO == EINTR) /* Interrupted system call - we'll just try again */ goto retry4; ! strncpy(errbuf, ! "PQrequestCancel() -- send() failed: ", errbufsize); goto cancel_errReturn; } *************** *** 2316,2324 **** return TRUE; cancel_errReturn: ! strcat(conn->errorMessage.data, SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); ! strcat(conn->errorMessage.data, "\n"); ! conn->errorMessage.len = strlen(conn->errorMessage.data); if (tmpsock >= 0) { closesocket(tmpsock); --- 2351,2361 ---- 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; ! strncat(errbuf, SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)), maxlen); ! strcat(errbuf, "\n"); if (tmpsock >= 0) { closesocket(tmpsock); *************** *** 2332,2337 **** --- 2369,2417 ---- } + int + PQcancel(PGcancel *cancel, char *errbuf, int errbufsize) + { + int r; + + if (!cancel) + return FALSE; + + /* internal_cancel will save/restore errno for us */ + r = internal_cancel(&cancel->raddr, cancel->be_pid, cancel->be_key, + errbuf, errbufsize); + + return r; + } + + int + PQrequestCancel(PGconn *conn) + { + int r; + + /* Check we have an open connection */ + if (!conn) + return FALSE; + + if (!conn->sock) + { + strcpy(conn->errorMessage.data, + "PQrequestCancel() -- connection is not open\n"); + conn->errorMessage.len = strlen(conn->errorMessage.data); + + return FALSE; + } + + /* internal_cancel will save/restore errno for us */ + r = internal_cancel(&conn->raddr, conn->be_pid, conn->be_key, + conn->errorMessage.data, conn->errorMessage.maxlen); + if (!r) + conn->errorMessage.len = strlen(conn->errorMessage.data); + + return r; + } + + /* * pqPacketSend() -- convenience routine to send a message to server. * Index: interfaces/libpq/libpq-fe.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-fe.h,v retrieving revision 1.112 diff -c -r1.112 libpq-fe.h *** interfaces/libpq/libpq-fe.h 18 Oct 2004 22:00:42 -0000 1.112 --- interfaces/libpq/libpq-fe.h 30 Oct 2004 19:39:10 -0000 *************** *** 116,121 **** --- 116,127 ---- */ typedef struct pg_result PGresult; + /* PGcancel encapsulates the information needed to cancel a running + * query on an existing connection. + * The contents of this struct are not supposed to be known to applications. + */ + typedef struct pg_cancel PGcancel; + /* PGnotify represents the occurrence of a NOTIFY message. * Ideally this would be an opaque typedef, but it's so simple that it's * unlikely to change. *************** *** 234,240 **** --- 240,255 ---- /* Synchronous (blocking) */ extern void PQreset(PGconn *conn); + /* request a cancel structure */ + extern PGcancel *PQgetCancel(PGconn *conn); + + /* free a cancel structure */ + extern void PQfreeCancel(PGcancel *cancel); + /* issue a cancel request */ + extern int PQcancel(PGcancel *cancel, char *errbuf, int errbufsize); + + /* backwards compatible version, not as thread safe */ extern int PQrequestCancel(PGconn *conn); /* Accessor functions for PGconn objects */ Index: interfaces/libpq/libpq-int.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-int.h,v retrieving revision 1.95 diff -c -r1.95 libpq-int.h *** interfaces/libpq/libpq-int.h 18 Oct 2004 22:00:42 -0000 1.95 --- interfaces/libpq/libpq-int.h 30 Oct 2004 19:38:26 -0000 *************** *** 346,351 **** --- 346,363 ---- PQExpBufferData workBuffer; /* expansible string */ }; + /* PGcancel stores all data necessary to cancel a connection. A copy of this + * data is required to safely cancel a connection running on a different + * thread. + */ + struct pg_cancel + { + SockAddr raddr; /* Remote address */ + int be_pid; /* PID of backend --- needed for cancels */ + int be_key; /* key of backend --- needed for cancels */ + }; + + /* String descriptions of the ExecStatusTypes. * direct use of this array is deprecated; call PQresStatus() instead. */