Introduce interruptible_select

We have places where we call a blocking gdb_select expecting that a
Ctrl-C will unblock it.  However, if the Ctrl-C is pressed just before
gdb_select, the SIGINT handler runs before gdb_select, and thus
gdb_select won't return.

For example gdb_readline_no_editing:

       QUIT;

       /* Wait until at least one byte of data is available.  Control-C
          can interrupt gdb_select, but not fgetc.  */
       FD_ZERO (&readfds);
       FD_SET (fd, &readfds);
       if (gdb_select (fd + 1, &readfds, NULL, NULL, NULL) == -1)

and stdio_file_read:

     /* For the benefit of Windows, call gdb_select before reading from
	the file.  Wait until at least one byte of data is available.
	Control-C can interrupt gdb_select, but not read.  */
     {
       fd_set readfds;
       FD_ZERO (&readfds);
       FD_SET (stdio->fd, &readfds);
       if (gdb_select (stdio->fd + 1, &readfds, NULL, NULL, NULL) == -1)
	 return -1;
     }
     return read (stdio->fd, buf, length_buf);


This is a race classically fixed with either the self-pipe trick, or
by blocking SIGINT and then using pselect instead of select.

Blocking SIGINT most of the time would mean that check_quit_flag (and
thus QUIT) would need to do a syscall every time it is called, which
sounds best avoided, since QUIT is called in many loops.  Thus we take
the self-pipe trick route (wrapped in a serial event).

Instead of having all places that need this manually add an extra file
descriptor to the set of gdb_select's watched file descriptors, we
introduce a wrapper, interruptible_select, that does that.

The Windows version of gdb_select actually does not suffer from this,
because mingw-hdep.c:gdb_call_async_signal_handler sets a Windows
event that gdb_select always waits on.  So this patch can be seen as
generalization of that technique.  We can't remove that extra event
from mingw-hdep.c until we get rid of immediate_quit though.

gdb/ChangeLog:
2016-04-12  Pedro Alves  <palves@redhat.com>

	* defs.h: Extend QUIT-related comments to mention
	interruptible_select.
	(quit_serial_event_set, quit_serial_event_clear): Declare.
	* event-top.c: Include "ser-event.h" and "gdb_select.h".
	(quit_serial_event): New global.
	(async_init_signals): Make quit_serial_event.
	(quit_serial_event_set, quit_serial_event_clear)
	(quit_serial_event_fd, interruptible_select): New functions.
	* extension.c (set_quit_flag): Set the quit serial event.
	(check_quit_flag): Clear the quit serial event.
	* gdb_select.h (interruptible_select): New declaration.
	* guile/scm-ports.c (ioscm_input_waiting): Use
	interruptible_select instead of gdb_select.
	* top.c (gdb_readline_no_editing): Likewise.
	* ui-file.c (stdio_file_read): Likewise.
This commit is contained in:
Pedro Alves
2016-04-12 16:49:30 +01:00
parent 5cc3ce8b5f
commit f0881b37b6
8 changed files with 140 additions and 10 deletions

View File

@ -1,3 +1,21 @@
2016-04-12 Pedro Alves <palves@redhat.com>
* defs.h: Extend QUIT-related comments to mention
interruptible_select.
(quit_serial_event_set, quit_serial_event_clear): Declare.
* event-top.c: Include "ser-event.h" and "gdb_select.h".
(quit_serial_event): New global.
(async_init_signals): Make quit_serial_event.
(quit_serial_event_set, quit_serial_event_clear)
(quit_serial_event_fd, interruptible_select): New functions.
* extension.c (set_quit_flag): Set the quit serial event.
(check_quit_flag): Clear the quit serial event.
* gdb_select.h (interruptible_select): New declaration.
* guile/scm-ports.c (ioscm_input_waiting): Use
interruptible_select instead of gdb_select.
* top.c (gdb_readline_no_editing): Likewise.
* ui-file.c (stdio_file_read): Likewise.
2016-04-12 Pedro Alves <palves@redhat.com> 2016-04-12 Pedro Alves <palves@redhat.com>
* event-loop.c: Include "ser-event.h". * event-loop.c: Include "ser-event.h".

View File

@ -131,6 +131,11 @@ extern char *debug_file_directory;
take a long time, and which ought to be interruptible, checks this take a long time, and which ought to be interruptible, checks this
flag using the QUIT macro. flag using the QUIT macro.
In addition to setting a flag, the SIGINT handler also marks a
select/poll-able file descriptor as read-ready. That is used by
interruptible_select in order to support interrupting blocking I/O
in a race-free manner.
These functions use the extension_language_ops API to allow extension These functions use the extension_language_ops API to allow extension
language(s) and GDB SIGINT handling to coexist seamlessly. */ language(s) and GDB SIGINT handling to coexist seamlessly. */
@ -159,6 +164,12 @@ extern void maybe_quit (void);
connection. */ connection. */
#define QUIT maybe_quit () #define QUIT maybe_quit ()
/* Set the serial event associated with the quit flag. */
extern void quit_serial_event_set (void);
/* Clear the serial event associated with the quit flag. */
extern void quit_serial_event_clear (void);
/* * Languages represented in the symbol table and elsewhere. /* * Languages represented in the symbol table and elsewhere.
This should probably be in language.h, but since enum's can't This should probably be in language.h, but since enum's can't
be forward declared to satisfy opaque references before their be forward declared to satisfy opaque references before their

View File

@ -38,6 +38,8 @@
#include "annotate.h" #include "annotate.h"
#include "maint.h" #include "maint.h"
#include "buffer.h" #include "buffer.h"
#include "ser-event.h"
#include "gdb_select.h"
/* readline include files. */ /* readline include files. */
#include "readline/readline.h" #include "readline/readline.h"
@ -733,6 +735,12 @@ gdb_readline_no_editing_callback (gdb_client_data client_data)
} }
/* The serial event associated with the QUIT flag. set_quit_flag sets
this, and check_quit_flag clears it. Used by interruptible_select
to be able to do interruptible I/O with no race with the SIGINT
handler. */
static struct serial_event *quit_serial_event;
/* Initialization of signal handlers and tokens. There is a function /* Initialization of signal handlers and tokens. There is a function
handle_sig* for each of the signals GDB cares about. Specifically: handle_sig* for each of the signals GDB cares about. Specifically:
SIGINT, SIGFPE, SIGQUIT, SIGTSTP, SIGHUP, SIGWINCH. These SIGINT, SIGFPE, SIGQUIT, SIGTSTP, SIGHUP, SIGWINCH. These
@ -750,6 +758,8 @@ async_init_signals (void)
{ {
initialize_async_signal_handlers (); initialize_async_signal_handlers ();
quit_serial_event = make_serial_event ();
signal (SIGINT, handle_sigint); signal (SIGINT, handle_sigint);
sigint_token = sigint_token =
create_async_signal_handler (async_request_quit, NULL); create_async_signal_handler (async_request_quit, NULL);
@ -794,8 +804,33 @@ async_init_signals (void)
#endif #endif
} }
/* Tell the event loop what to do if SIGINT is received. /* See defs.h. */
See event-signal.c. */
void
quit_serial_event_set (void)
{
serial_event_set (quit_serial_event);
}
/* See defs.h. */
void
quit_serial_event_clear (void)
{
serial_event_clear (quit_serial_event);
}
/* Return the selectable file descriptor of the serial event
associated with the quit flag. */
static int
quit_serial_event_fd (void)
{
return serial_event_fd (quit_serial_event);
}
/* Handle a SIGINT. */
void void
handle_sigint (int sig) handle_sigint (int sig)
{ {
@ -819,6 +854,42 @@ handle_sigint (int sig)
gdb_call_async_signal_handler (sigint_token, immediate_quit); gdb_call_async_signal_handler (sigint_token, immediate_quit);
} }
/* See gdb_select.h. */
int
interruptible_select (int n,
fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout)
{
fd_set my_readfds;
int fd;
int res;
if (readfds == NULL)
{
readfds = &my_readfds;
FD_ZERO (&my_readfds);
}
fd = quit_serial_event_fd ();
FD_SET (fd, readfds);
if (n <= fd)
n = fd + 1;
do
{
res = gdb_select (n, readfds, writefds, exceptfds, timeout);
}
while (res == -1 && errno == EINTR);
if (res == 1 && FD_ISSET (fd, readfds))
{
errno = EINTR;
return -1;
}
return res;
}
/* Handle GDB exit upon receiving SIGTERM if target_can_async_p (). */ /* Handle GDB exit upon receiving SIGTERM if target_can_async_p (). */
static void static void

View File

@ -828,7 +828,16 @@ set_quit_flag (void)
&& active_ext_lang->ops->set_quit_flag != NULL) && active_ext_lang->ops->set_quit_flag != NULL)
active_ext_lang->ops->set_quit_flag (active_ext_lang); active_ext_lang->ops->set_quit_flag (active_ext_lang);
else else
quit_flag = 1; {
quit_flag = 1;
/* Now wake up the event loop, or any interruptible_select. Do
this after setting the flag, because signals on Windows
actually run on a separate thread, and thus otherwise the
main code could be woken up and find quit_flag still
clear. */
quit_serial_event_set ();
}
} }
/* Return true if the quit flag has been set, false otherwise. /* Return true if the quit flag has been set, false otherwise.
@ -852,6 +861,10 @@ check_quit_flag (void)
/* This is written in a particular way to avoid races. */ /* This is written in a particular way to avoid races. */
if (quit_flag) if (quit_flag)
{ {
/* No longer need to wake up the event loop or any
interruptible_select. The caller handles the quit
request. */
quit_serial_event_clear ();
quit_flag = 0; quit_flag = 0;
result = 1; result = 1;
} }

View File

@ -33,4 +33,19 @@
extern int gdb_select (int n, fd_set *readfds, fd_set *writefds, extern int gdb_select (int n, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout); fd_set *exceptfds, struct timeval *timeout);
/* Convenience wrapper around gdb_select that returns -1/EINTR if
set_quit_flag is set, either on entry or from a signal handler or
from a different thread while select is blocked. The quit flag is
not cleared on exit -- the caller is responsible to check it with
check_quit_flag or QUIT.
Note this does NOT return -1/EINTR if any signal handler other than
SIGINT runs, nor if the current SIGINT handler does not call
set_quit_flag. */
extern int interruptible_select (int n,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout);
#endif /* !defined(GDB_SELECT_H) */ #endif /* !defined(GDB_SELECT_H) */

View File

@ -201,7 +201,9 @@ ioscm_input_waiting (SCM port)
FD_ZERO (&input_fds); FD_ZERO (&input_fds);
FD_SET (fdes, &input_fds); FD_SET (fdes, &input_fds);
num_found = gdb_select (num_fds, &input_fds, NULL, NULL, &timeout); num_found = interruptible_select (num_fds,
&input_fds, NULL, NULL,
&timeout);
if (num_found < 0) if (num_found < 0)
{ {
/* Guile doesn't export SIGINT hooks like Python does. /* Guile doesn't export SIGINT hooks like Python does.

View File

@ -613,10 +613,10 @@ gdb_readline_no_editing (const char *prompt)
QUIT; QUIT;
/* Wait until at least one byte of data is available. Control-C /* Wait until at least one byte of data is available. Control-C
can interrupt gdb_select, but not fgetc. */ can interrupt interruptible_select, but not fgetc. */
FD_ZERO (&readfds); FD_ZERO (&readfds);
FD_SET (fd, &readfds); FD_SET (fd, &readfds);
if (gdb_select (fd + 1, &readfds, NULL, NULL, NULL) == -1) if (interruptible_select (fd + 1, &readfds, NULL, NULL, NULL) == -1)
{ {
if (errno == EINTR) if (errno == EINTR)
{ {

View File

@ -567,14 +567,14 @@ stdio_file_read (struct ui_file *file, char *buf, long length_buf)
internal_error (__FILE__, __LINE__, internal_error (__FILE__, __LINE__,
_("stdio_file_read: bad magic number")); _("stdio_file_read: bad magic number"));
/* For the benefit of Windows, call gdb_select before reading from /* Wait until at least one byte of data is available, or we get
the file. Wait until at least one byte of data is available. interrupted with Control-C. */
Control-C can interrupt gdb_select, but not read. */
{ {
fd_set readfds; fd_set readfds;
FD_ZERO (&readfds); FD_ZERO (&readfds);
FD_SET (stdio->fd, &readfds); FD_SET (stdio->fd, &readfds);
if (gdb_select (stdio->fd + 1, &readfds, NULL, NULL, NULL) == -1) if (interruptible_select (stdio->fd + 1, &readfds, NULL, NULL, NULL) == -1)
return -1; return -1;
} }