Revamp and modernize X error traps

* add per-display gdk_x11_display_error_trap_push()
  (X11-specific because gdk_error_trap_push() probably
  should have been)
* make gdk_error_trap_push() handle only GDK displays
  not displays opened without a GDK wrapper
* make gdk_error_trap_pop() and gdk_x11_display_error_trap_pop()
  automatically sync only if needed, so manual gdk_flush() is not
  required
* add gdk_error_trap_pop_ignored() which just asynchronously
  ignores errors, so never needs to sync
* add G_GNUC_WARN_UNUSED_RESULT to plain pop(), because
  if you use plain pop() and don't need the return value,
  the async gdk_error_trap_pop_ignored() should be used
  instead. This results in lots of warnings to clean
  up in a later patch.

The main objective here was to avoid the need to sync just
to ignore an error. Now, syncing is automatic, and only
happens when we need to know the error code.

https://bugzilla.gnome.org/show_bug.cgi?id=629608
This commit is contained in:
Havoc Pennington 2010-09-18 18:19:27 -04:00 committed by Matthias Clasen
parent 4f3e5e6ebc
commit b837ef5a6d
11 changed files with 488 additions and 132 deletions

View File

@ -326,42 +326,6 @@ available.
@void:
<!-- ##### FUNCTION gdk_error_trap_push ##### -->
<para>
This function allows X errors to be trapped instead of the normal behavior
of exiting the application. It should only be used if it is not possible to
avoid the X error in any other way.
</para>
<example>
<title>Trapping an X error</title>
<programlisting>
gdk_error_trap_push (<!-- -->);
/* ... Call the X function which may cause an error here ... */
/* Flush the X queue to catch errors now. */
gdk_flush (<!-- -->);
if (gdk_error_trap_pop (<!-- -->))
{
/* ... Handle the error here ... */
}
</programlisting>
</example>
@void:
<!-- ##### FUNCTION gdk_error_trap_pop ##### -->
<para>
Removes the X error trap installed with gdk_error_trap_push().
</para>
@void:
@Returns: the X error code, or 0 if no error occurred.
<!-- ##### MACRO GDK_WINDOWING_X11 ##### -->
<para>
This macro is defined if GDK is configured to use the X11 backend.

View File

@ -81,8 +81,11 @@ void gdk_set_program_class (const char *program_class);
/* Push and pop error handlers for X errors
*/
void gdk_error_trap_push (void);
gint gdk_error_trap_pop (void);
void gdk_error_trap_push (void);
/* warn unused because you could use pop_ignored otherwise */
G_GNUC_WARN_UNUSED_RESULT gint gdk_error_trap_pop (void);
void gdk_error_trap_pop_ignored (void);
gchar* gdk_get_display (void);
G_CONST_RETURN gchar* gdk_get_display_arg_name (void);

View File

@ -33,8 +33,6 @@
guint _gdk_debug_flags = 0;
gint _gdk_error_code = 0;
gint _gdk_error_warnings = TRUE;
GList *_gdk_default_filters = NULL;
gchar *_gdk_display_name = NULL;
gint _gdk_screen_number = -1;

View File

@ -88,8 +88,6 @@ typedef enum {
extern GList *_gdk_default_filters;
extern GdkWindow *_gdk_parent_root;
extern gint _gdk_error_code;
extern gint _gdk_error_warnings;
extern guint _gdk_debug_flags;
extern gboolean _gdk_native_windows;

View File

@ -56,6 +56,11 @@ gdk_error_trap_pop (void)
return 0;
}
void
gdk_error_trap_pop_ignored (void)
{
}
gchar *
gdk_get_display (void)
{

View File

@ -214,6 +214,11 @@ gdk_error_trap_pop (void)
return 0;
}
void
gdk_error_trap_pop_ignored (void)
{
}
void
gdk_notify_startup_complete (void)
{

View File

@ -24,6 +24,7 @@
#include "config.h"
#include <glib/gprintf.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
@ -67,6 +68,23 @@
#include <X11/extensions/Xrandr.h>
#endif
typedef struct _GdkErrorTrap GdkErrorTrap;
struct _GdkErrorTrap
{
/* Next sequence when trap was pushed, i.e. first sequence to
* ignore
*/
gulong start_sequence;
/* Next sequence when trap was popped, i.e. first sequence
* to not ignore. 0 if trap is still active.
*/
gulong end_sequence;
/* Most recent error code within the sequence */
int error_code;
};
static void gdk_display_x11_dispose (GObject *object);
static void gdk_display_x11_finalize (GObject *object);
@ -2657,3 +2675,250 @@ gdk_x11_register_standard_event_type (GdkDisplay *display,
display_x11->event_types = g_slist_prepend (display_x11->event_types, event_type);
}
/* compare X sequence numbers handling wraparound */
#define SEQUENCE_COMPARE(a,op,b) (((long) (a) - (long) (b)) op 0)
/* delivers an error event from the error handler in gdkmain-x11.c */
void
_gdk_x11_display_error_event (GdkDisplay *display,
XErrorEvent *error)
{
GdkDisplayX11 *display_x11;
GSList *tmp_list;
gboolean ignore;
display_x11 = GDK_DISPLAY_X11 (display);
ignore = FALSE;
for (tmp_list = display_x11->error_traps;
tmp_list != NULL;
tmp_list = tmp_list->next)
{
GdkErrorTrap *trap;
trap = tmp_list->data;
if (SEQUENCE_COMPARE (trap->start_sequence, <=, error->serial) &&
(trap->end_sequence == 0 ||
SEQUENCE_COMPARE (trap->end_sequence, >, error->serial)))
{
ignore = TRUE;
trap->error_code = error->error_code;
}
}
if (!ignore)
{
gchar buf[64];
gchar *msg;
XGetErrorText (display_x11->xdisplay, error->error_code, buf, 63);
msg =
g_strdup_printf ("The program '%s' received an X Window System error.\n"
"This probably reflects a bug in the program.\n"
"The error was '%s'.\n"
" (Details: serial %ld error_code %d request_code %d minor_code %d)\n"
" (Note to programmers: normally, X errors are reported asynchronously;\n"
" that is, you will receive the error a while after causing it.\n"
" To debug your program, run it with the --sync command line\n"
" option to change this behavior. You can then get a meaningful\n"
" backtrace from your debugger if you break on the gdk_x_error() function.)",
g_get_prgname (),
buf,
error->serial,
error->error_code,
error->request_code,
error->minor_code);
#ifdef G_ENABLE_DEBUG
g_error ("%s", msg);
#else /* !G_ENABLE_DEBUG */
g_fprintf (stderr, "%s\n", msg);
exit (1);
#endif /* G_ENABLE_DEBUG */
}
}
static void
delete_outdated_error_traps (GdkDisplayX11 *display_x11)
{
GSList *tmp_list;
gulong processed_sequence;
processed_sequence = XLastKnownRequestProcessed (display_x11->xdisplay);
tmp_list = display_x11->error_traps;
while (tmp_list != NULL)
{
GdkErrorTrap *trap = tmp_list->data;
if (trap->end_sequence != 0 &&
SEQUENCE_COMPARE (trap->end_sequence, <, processed_sequence))
{
GSList *free_me = tmp_list;
tmp_list = tmp_list->next;
display_x11->error_traps =
g_slist_delete_link (display_x11->error_traps, free_me);
g_slice_free (GdkErrorTrap, trap);
}
else
{
tmp_list = tmp_list->next;
}
}
}
/**
* gdk_x11_display_error_trap_push:
*
* Begins a range of X requests for which X error events will be
* ignored. Unignored errors (when no trap is pushed) will abort the
* application.
*
* See also gdk_error_trap_push() to push a trap on all displays.
*
* Since: 3.0
*/
void
gdk_x11_display_error_trap_push (GdkDisplay *display)
{
GdkDisplayX11 *display_x11;
GdkErrorTrap *trap;
display_x11 = GDK_DISPLAY_X11 (display);
delete_outdated_error_traps (display_x11);
/* set up the Xlib callback to tell us about errors */
_gdk_x11_error_handler_push ();
trap = g_slice_new0 (GdkErrorTrap);
trap->start_sequence = XNextRequest (display_x11->xdisplay);
trap->error_code = Success;
display_x11->error_traps =
g_slist_prepend (display_x11->error_traps, trap);
}
static gint
gdk_x11_display_error_trap_pop_internal (GdkDisplay *display,
gboolean need_code)
{
GdkDisplayX11 *display_x11;
GdkErrorTrap *trap;
GSList *tmp_list;
int result;
display_x11 = GDK_DISPLAY_X11 (display);
g_return_val_if_fail (display_x11->error_traps != NULL, Success);
/* Find the first trap that hasn't been popped already */
trap = NULL; /* quiet gcc */
for (tmp_list = display_x11->error_traps;
tmp_list != NULL;
tmp_list = tmp_list->next)
{
trap = tmp_list->data;
if (trap->end_sequence == 0)
break;
}
g_return_val_if_fail (trap != NULL, Success);
g_assert (trap->end_sequence == 0);
/* May need to sync to fill in trap->error_code if we care about
* getting an error code.
*/
if (need_code)
{
gulong processed_sequence;
gulong next_sequence;
next_sequence = XNextRequest (display_x11->xdisplay);
processed_sequence = XLastKnownRequestProcessed (display_x11->xdisplay);
/* If our last request was already processed, there is no point
* in syncing. i.e. if last request was a round trip (or even if
* we got an event with the serial of a non-round-trip)
*/
if ((next_sequence - 1) != processed_sequence)
{
XSync (display_x11->xdisplay, False);
}
result = trap->error_code;
}
else
{
result = Success;
}
/* record end of trap, giving us a range of
* error sequences we'll ignore.
*/
trap->end_sequence = XNextRequest (display_x11->xdisplay);
/* remove the Xlib callback */
_gdk_x11_error_handler_pop ();
/* we may already be outdated */
delete_outdated_error_traps (display_x11);
return result;
}
/**
* gdk_x11_display_error_trap_pop:
* @display: the display
*
* Pops the error trap pushed by gdk_x11_display_error_trap_push().
* Will XSync() if necessary and will always block until
* the error is known to have occurred or not occurred,
* so the error code can be returned.
*
* If you don't need to use the return value,
* gdk_x11_display_error_trap_pop_ignored() would be more efficient.
*
* See gdk_error_trap_pop() for the all-displays-at-once
* equivalent.
*
* Since: 3.0
*
* Return value: X error code or 0 on success
*/
gint
gdk_x11_display_error_trap_pop (GdkDisplay *display)
{
g_return_val_if_fail (GDK_IS_DISPLAY_X11 (display), Success);
return gdk_x11_display_error_trap_pop_internal (display, TRUE);
}
/**
* gdk_x11_display_error_trap_pop_ignored:
* @display: the display
*
* Pops the error trap pushed by gdk_x11_display_error_trap_push().
* Does not block to see if an error occurred; merely records the
* range of requests to ignore errors for, and ignores those errors
* if they arrive asynchronously.
*
* See gdk_error_trap_pop_ignored() for the all-displays-at-once
* equivalent.
*
* Since: 3.0
*/
void
gdk_x11_display_error_trap_pop_ignored (GdkDisplay *display)
{
g_return_if_fail (GDK_IS_DISPLAY_X11 (display));
gdk_x11_display_error_trap_pop_internal (display, FALSE);
}

View File

@ -141,6 +141,8 @@ struct _GdkDisplayX11
/* The offscreen window that has the pointer in it (if any) */
GdkWindow *active_offscreen_window;
GSList *error_traps;
};
struct _GdkDisplayX11Class
@ -149,8 +151,10 @@ struct _GdkDisplayX11Class
};
GType _gdk_display_x11_get_type (void);
GdkScreen *_gdk_x11_display_screen_for_xrootwin (GdkDisplay *display,
Window xrootwin);
GdkScreen *_gdk_x11_display_screen_for_xrootwin (GdkDisplay *display,
Window xrootwin);
void _gdk_x11_display_error_event (GdkDisplay *display,
XErrorEvent *error);
G_END_DECLS

View File

@ -52,8 +52,8 @@
#include <gdk/gdkdeviceprivate.h>
typedef struct _GdkPredicate GdkPredicate;
typedef struct _GdkErrorTrap GdkErrorTrap;
typedef struct _GdkPredicate GdkPredicate;
typedef struct _GdkGlobalErrorTrap GdkGlobalErrorTrap;
struct _GdkPredicate
{
@ -61,11 +61,14 @@ struct _GdkPredicate
gpointer data;
};
struct _GdkErrorTrap
/* non-GDK previous error handler */
static int (*_gdk_old_error_handler) (Display *, XErrorEvent *);
/* number of times we've pushed the GDK error handler */
static int _gdk_error_handler_push_count = 0;
struct _GdkGlobalErrorTrap
{
int (*old_handler) (Display *, XErrorEvent *);
gint error_warnings;
gint error_code;
GSList *displays;
};
/*
@ -284,72 +287,6 @@ _gdk_windowing_exit (void)
}
}
/*
*--------------------------------------------------------------
* gdk_x_error
*
* The X error handling routine.
*
* Arguments:
* "display" is the X display the error originated from.
* "error" is the XErrorEvent that we are handling.
*
* Results:
* Either we were expecting some sort of error to occur,
* in which case we set the "_gdk_error_code" flag, or this
* error was unexpected, in which case we will print an
* error message and exit. (Since trying to continue will
* most likely simply lead to more errors).
*
* Side effects:
*
*--------------------------------------------------------------
*/
static int
gdk_x_error (Display *display,
XErrorEvent *error)
{
if (error->error_code)
{
if (_gdk_error_warnings)
{
gchar buf[64];
gchar *msg;
XGetErrorText (display, error->error_code, buf, 63);
msg =
g_strdup_printf ("The program '%s' received an X Window System error.\n"
"This probably reflects a bug in the program.\n"
"The error was '%s'.\n"
" (Details: serial %ld error_code %d request_code %d minor_code %d)\n"
" (Note to programmers: normally, X errors are reported asynchronously;\n"
" that is, you will receive the error a while after causing it.\n"
" To debug your program, run it with the --sync command line\n"
" option to change this behavior. You can then get a meaningful\n"
" backtrace from your debugger if you break on the gdk_x_error() function.)",
g_get_prgname (),
buf,
error->serial,
error->error_code,
error->request_code,
error->minor_code);
#ifdef G_ENABLE_DEBUG
g_error ("%s", msg);
#else /* !G_ENABLE_DEBUG */
g_fprintf (stderr, "%s\n", msg);
exit (1);
#endif /* G_ENABLE_DEBUG */
}
_gdk_error_code = error->error_code;
}
return 0;
}
/*
*--------------------------------------------------------------
* gdk_x_io_error
@ -398,43 +335,212 @@ gdk_x_io_error (Display *display)
exit(1);
}
/* X error handler. Keep the name the same because people are used to
* breaking on it in the debugger.
*/
static int
gdk_x_error (Display *xdisplay,
XErrorEvent *error)
{
if (error->error_code)
{
GdkDisplay *error_display;
GdkDisplayManager *manager;
GSList *displays;
/* Figure out which GdkDisplay if any got the error. */
error_display = NULL;
manager = gdk_display_manager_get ();
displays = gdk_display_manager_list_displays (manager);
while (displays != NULL)
{
GdkDisplayX11 *gdk_display = displays->data;
if (xdisplay == gdk_display->xdisplay)
{
error_display = GDK_DISPLAY_OBJECT (gdk_display);
g_slist_free (displays);
displays = NULL;
}
else
{
displays = g_slist_delete_link (displays, displays);
}
}
if (error_display == NULL)
{
/* Error on an X display not opened by GDK. Ignore. */
return 0;
}
else
{
_gdk_x11_display_error_event (error_display, error);
}
}
return 0;
}
void
_gdk_x11_error_handler_push (void)
{
_gdk_old_error_handler = XSetErrorHandler (gdk_x_error);
if (_gdk_error_handler_push_count > 0)
{
if (_gdk_old_error_handler != gdk_x_error)
g_warning ("XSetErrorHandler() called with a GDK error trap pushed. Don't do that.");
}
_gdk_error_handler_push_count += 1;
}
void
_gdk_x11_error_handler_pop (void)
{
g_return_if_fail (_gdk_error_handler_push_count > 0);
_gdk_error_handler_push_count -= 1;
if (_gdk_error_handler_push_count == 0)
{
XSetErrorHandler (_gdk_old_error_handler);
_gdk_old_error_handler = NULL;
}
}
/**
* gdk_error_trap_push:
*
* This function allows X errors to be trapped instead of the normal
* behavior of exiting the application. It should only be used if it
* is not possible to avoid the X error in any other way. Errors are
* ignored on all #GdkDisplay currently known to the
* #GdkDisplayManager. If you don't care which error happens and just
* want to ignore everything, pop with gdk_error_trap_pop_ignored().
* If you need the error code, use gdk_error_trap_pop() which may have
* to block and wait for the error to arrive from the X server.
*
* This API exists on all platforms but only does anything on X.
*
* You can use gdk_x11_display_error_trap_push() to ignore errors
* on only a single display.
*
* <example>
* <title>Trapping an X error</title>
* <programlisting>
* gdk_error_trap_push (<!-- -->);
*
* // ... Call the X function which may cause an error here ...
*
*
* if (gdk_error_trap_pop (<!-- -->))
* {
* // ... Handle the error here ...
* }
* </programlisting>
* </example>
*
*/
void
gdk_error_trap_push (void)
{
GdkErrorTrap *trap;
GdkGlobalErrorTrap *trap;
GdkDisplayManager *manager;
GSList *tmp_list;
trap = g_slice_new (GdkErrorTrap);
trap = g_slice_new (GdkGlobalErrorTrap);
manager = gdk_display_manager_get ();
trap->displays = gdk_display_manager_list_displays (manager);
trap->old_handler = XSetErrorHandler (gdk_x_error);
trap->error_code = _gdk_error_code;
trap->error_warnings = _gdk_error_warnings;
g_slist_foreach (trap->displays, (GFunc) g_object_ref, NULL);
for (tmp_list = trap->displays;
tmp_list != NULL;
tmp_list = tmp_list->next)
{
gdk_x11_display_error_trap_push (tmp_list->data);
}
g_queue_push_head (&gdk_error_traps, trap);
_gdk_error_code = 0;
_gdk_error_warnings = 0;
}
gint
gdk_error_trap_pop (void)
static gint
gdk_error_trap_pop_internal (gboolean need_code)
{
GdkErrorTrap *trap;
GdkGlobalErrorTrap *trap;
gint result;
GSList *tmp_list;
trap = g_queue_pop_head (&gdk_error_traps);
g_return_val_if_fail (trap != NULL, 0);
g_return_val_if_fail (trap != NULL, Success);
result = _gdk_error_code;
result = Success;
for (tmp_list = trap->displays;
tmp_list != NULL;
tmp_list = tmp_list->next)
{
gint code = Success;
_gdk_error_code = trap->error_code;
_gdk_error_warnings = trap->error_warnings;
XSetErrorHandler (trap->old_handler);
if (need_code)
code = gdk_x11_display_error_trap_pop (tmp_list->data);
else
gdk_x11_display_error_trap_pop_ignored (tmp_list->data);
g_slice_free (GdkErrorTrap, trap);
/* we use the error on the last display listed, why not. */
if (code != Success)
result = code;
}
g_slist_foreach (trap->displays, (GFunc) g_object_unref, NULL);
g_slist_free (trap->displays);
g_slice_free (GdkGlobalErrorTrap, trap);
return result;
}
/**
* gdk_error_trap_pop_ignored:
*
* Removes an error trap pushed with gdk_error_trap_push(), but
* without bothering to wait and see whether an error occurred. If an
* error arrives later asynchronously that was triggered while the
* trap was pushed, that error will be ignored.
*
* Since: 3.0
*/
void
gdk_error_trap_pop_ignored (void)
{
gdk_error_trap_pop_internal (FALSE);
}
/**
* gdk_error_trap_pop:
*
* Removes an error trap pushed with gdk_error_trap_push().
* May block until an error has been definitively received
* or not received from the X server. gdk_error_trap_pop_ignored()
* is preferred if you don't need to know whether an error
* occurred, because it never has to block. If you don't
* need the return value of gdk_error_trap_pop(), use
* gdk_error_trap_pop_ignored().
*
* Prior to GDK 3.0, this function would not automatically
* sync for you, so you had to gdk_flush() if your last
* call to Xlib was not a blocking round trip.
*
* Return value: X error code or 0 on success
*/
gint
gdk_error_trap_pop (void)
{
return gdk_error_trap_pop_internal (TRUE);
}
gchar *
gdk_get_display (void)
{

View File

@ -60,6 +60,8 @@ struct _GdkVisualPrivate
GdkScreen *screen;
};
void _gdk_x11_error_handler_push (void);
void _gdk_x11_error_handler_pop (void);
void _gdk_xid_table_insert (GdkDisplay *display,
XID *xid,

View File

@ -164,6 +164,12 @@ G_CONST_RETURN gchar *gdk_x11_get_xatom_name (Atom xatom);
void gdk_x11_display_grab (GdkDisplay *display);
void gdk_x11_display_ungrab (GdkDisplay *display);
void gdk_x11_display_error_trap_push (GdkDisplay *display);
/* warn unused because you could use pop_ignored otherwise */
G_GNUC_WARN_UNUSED_RESULT gint gdk_x11_display_error_trap_pop (GdkDisplay *display);
void gdk_x11_display_error_trap_pop_ignored (GdkDisplay *display);
void gdk_x11_register_standard_event_type (GdkDisplay *display,
gint event_base,
gint n_events);