33 *
44 * Copyright (c) 2000-2006, PostgreSQL Global Development Group
55 *
6- * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.118 2006/05/26 19:51:29 tgl Exp $
6+ * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.119 2006/06/14 16:49:02 tgl Exp $
77 */
88#include "postgres_fe.h"
99#include "common.h"
1616#ifndef WIN32
1717#include <sys/time.h>
1818#include <unistd.h> /* for write() */
19- #include <setjmp.h>
2019#else
2120#include <io.h> /* for _write() */
2221#include <win32.h>
@@ -58,8 +57,6 @@ typedef struct _timeb TimevalStruct;
5857 ((T)->millitm - (U)->millitm))
5958#endif
6059
61- extern bool prompt_state ;
62-
6360
6461static bool command_no_begin (const char * query );
6562
@@ -219,55 +216,79 @@ NoticeProcessor(void *arg, const char *message)
219216/*
220217 * Code to support query cancellation
221218 *
222- * Before we start a query, we enablea SIGINT signal catcherthat sends a
219+ * Before we start a query, we enablethe SIGINT signal catcherto send a
223220 * cancel request to the backend. Note that sending the cancel directly from
224221 * the signal handler is safe because PQcancel() is written to make it
225- * so. We use write() toprint to stderr because it's better to use simple
222+ * so. We use write() toreport to stderr because it's better to use simple
226223 * facilities in a signal handler.
227224 *
228225 * On win32, the signal cancelling happens on a separate thread, because
229226 * that's how SetConsoleCtrlHandler works. The PQcancel function is safe
230227 * for this (unlike PQrequestCancel). However, a CRITICAL_SECTION is required
231- * to protect the PGcancel structure against being changed while theother
228+ * to protect the PGcancel structure against being changed while thesignal
232229 * thread is using it.
230+ *
231+ * SIGINT is supposed to abort all long-running psql operations, not only
232+ * database queries. In most places, this is accomplished by checking
233+ * cancel_pressed during long-running loops. However, that won't work when
234+ * blocked on user input (in readline() or fgets()). In those places, we
235+ * set sigint_interrupt_enabled TRUE while blocked, instructing the signal
236+ * catcher to longjmp through sigint_interrupt_jmp. We assume readline and
237+ * fgets are coded to handle possible interruption. (XXX currently this does
238+ * not work on win32, so control-C is less useful there)
233239 */
234- static PGcancel * cancelConn = NULL ;
240+ volatile bool sigint_interrupt_enabled = false;
241+
242+ sigjmp_buf sigint_interrupt_jmp ;
243+
244+ static PGcancel * volatile cancelConn = NULL ;
235245
236246#ifdef WIN32
237247static CRITICAL_SECTION cancelConnLock ;
238248#endif
239249
240- volatile bool cancel_pressed = false;
241-
242250#define write_stderr (str )write(fileno(stderr), str, strlen(str))
243251
244252
245253#ifndef WIN32
246254
247- void
255+ static void
248256handle_sigint (SIGNAL_ARGS )
249257{
250258int save_errno = errno ;
251259char errbuf [256 ];
252260
253- /*Don't muck around if prompting fora password. */
254- if (prompt_state )
255- return ;
256-
257- if ( cancelConn == NULL )
258- siglongjmp ( main_loop_jmp , 1 );
261+ /*if we are waiting forinput, longjmp out of it */
262+ if (sigint_interrupt_enabled )
263+ {
264+ sigint_interrupt_enabled = false;
265+ siglongjmp ( sigint_interrupt_jmp , 1 );
266+ }
259267
268+ /* else, set cancel flag to stop any long-running loops */
260269cancel_pressed = true;
261270
262- if (PQcancel (cancelConn ,errbuf ,sizeof (errbuf )))
263- write_stderr ("Cancel request sent\n" );
264- else
271+ /* and send QueryCancel if we are processing a database query */
272+ if (cancelConn != NULL )
265273{
266- write_stderr ("Could not send cancel request: " );
267- write_stderr (errbuf );
274+ if (PQcancel (cancelConn ,errbuf ,sizeof (errbuf )))
275+ write_stderr ("Cancel request sent\n" );
276+ else
277+ {
278+ write_stderr ("Could not send cancel request: " );
279+ write_stderr (errbuf );
280+ }
268281}
282+
269283errno = save_errno ;/* just in case the write changed it */
270284}
285+
286+ void
287+ setup_cancel_handler (void )
288+ {
289+ pqsignal (SIGINT ,handle_sigint );
290+ }
291+
271292#else /* WIN32 */
272293
273294static BOOL WINAPI
@@ -278,15 +299,17 @@ consoleHandler(DWORD dwCtrlType)
278299if (dwCtrlType == CTRL_C_EVENT ||
279300dwCtrlType == CTRL_BREAK_EVENT )
280301{
281- if (prompt_state )
282- return TRUE;
302+ /*
303+ * Can't longjmp here, because we are in wrong thread :-(
304+ */
305+
306+ /* set cancel flag to stop any long-running loops */
307+ cancel_pressed = true;
283308
284- /*Perform query cancel */
309+ /*and send QueryCancel if we are processing a database query */
285310EnterCriticalSection (& cancelConnLock );
286311if (cancelConn != NULL )
287312{
288- cancel_pressed = true;
289-
290313if (PQcancel (cancelConn ,errbuf ,sizeof (errbuf )))
291314write_stderr ("Cancel request sent\n" );
292315else
@@ -304,24 +327,14 @@ consoleHandler(DWORD dwCtrlType)
304327return FALSE;
305328}
306329
307- void
308- setup_win32_locks (void )
309- {
310- InitializeCriticalSection (& cancelConnLock );
311- }
312-
313330void
314331setup_cancel_handler (void )
315332{
316- static bool done = false ;
333+ InitializeCriticalSection ( & cancelConnLock ) ;
317334
318- /* only need one handler per process */
319- if (!done )
320- {
321- SetConsoleCtrlHandler (consoleHandler , TRUE);
322- done = true;
323- }
335+ SetConsoleCtrlHandler (consoleHandler , TRUE);
324336}
337+
325338#endif /* WIN32 */
326339
327340
@@ -386,16 +399,22 @@ CheckConnection(void)
386399 *
387400 * Set cancelConn to point to the current database connection.
388401 */
389- static void
402+ void
390403SetCancelConn (void )
391404{
405+ PGcancel * oldCancelConn ;
406+
392407#ifdef WIN32
393408EnterCriticalSection (& cancelConnLock );
394409#endif
395410
396411/* Free the old one if we have one */
397- if (cancelConn != NULL )
398- PQfreeCancel (cancelConn );
412+ oldCancelConn = cancelConn ;
413+ /* be sure handle_sigint doesn't use pointer while freeing */
414+ cancelConn = NULL ;
415+
416+ if (oldCancelConn != NULL )
417+ PQfreeCancel (oldCancelConn );
399418
400419cancelConn = PQgetCancel (pset .db );
401420
@@ -413,15 +432,19 @@ SetCancelConn(void)
413432void
414433ResetCancelConn (void )
415434{
435+ PGcancel * oldCancelConn ;
436+
416437#ifdef WIN32
417438EnterCriticalSection (& cancelConnLock );
418439#endif
419440
420- if (cancelConn )
421- PQfreeCancel (cancelConn );
422-
441+ oldCancelConn = cancelConn ;
442+ /* be sure handle_sigint doesn't use pointer while freeing */
423443cancelConn = NULL ;
424444
445+ if (oldCancelConn != NULL )
446+ PQfreeCancel (oldCancelConn );
447+
425448#ifdef WIN32
426449LeaveCriticalSection (& cancelConnLock );
427450#endif
@@ -453,15 +476,8 @@ AcceptResult(const PGresult *result, const char *query)
453476case PGRES_TUPLES_OK :
454477case PGRES_EMPTY_QUERY :
455478case PGRES_COPY_IN :
456- /* Fine, do nothing */
457- break ;
458-
459479case PGRES_COPY_OUT :
460- /*
461- * Keep cancel connection active during copy out state.
462- * The matching ResetCancelConn() is in handleCopyOut.
463- */
464- SetCancelConn ();
480+ /* Fine, do nothing */
465481break ;
466482
467483default :
@@ -648,12 +664,16 @@ ProcessCopyResult(PGresult *results)
648664break ;
649665
650666case PGRES_COPY_OUT :
667+ SetCancelConn ();
651668success = handleCopyOut (pset .db ,pset .queryFout );
669+ ResetCancelConn ();
652670break ;
653671
654672case PGRES_COPY_IN :
673+ SetCancelConn ();
655674success = handleCopyIn (pset .db ,pset .cur_cmd_source ,
656675PQbinaryTuples (results ));
676+ ResetCancelConn ();
657677break ;
658678
659679default :