xpra icon
Bug tracker and wiki

Debugging: error.py

File error.py, 6.7 KB (added by Antoine Martin, 5 years ago)

debug version of error.py which allows us to force sync calls, add logging, etc

Line 
1# This file is part of Parti.
2# Copyright (C) 2008, 2009 Nathaniel Smith <njs@pobox.com>
3# Parti is released under the terms of the GNU GPL v2, or, at your option, any
4# later version. See the file COPYING for details.
5
6# Goal: make it as easy and efficient as possible to manage the X errors that
7# a WM is inevitably susceptible to.  (E.g., if a window goes away while we
8# are working on it.)  On the one hand, we want to parcel operations into as
9# broad chunks as possible that at treated as succeeding or failing as a whole
10# (e.g., "setting up a new window", we don't really care how much was
11# accomplished before the failure occurred).  On the other, we do want to
12# check for X errors often, for use in debugging (esp., this makes it more
13# useful to run with -sync).
14#
15# The solution is to keep a stack of how deep we are in "transaction-like"
16# operations -- a transaction is a series of operations where we don't care if
17# we don't find about the failures until the end.  We only sync when exiting a
18# top-level transaction.
19#
20# The _synced and _unsynced variants differ in whether they assume the X
21# connection was left in a synchronized state by the code they called (e.g.,
22# if the last operation was an XGetProperty, then there is no need for us to
23# do another XSync).
24#
25# (In this modern world, with WM's either on the same machine or over
26# super-fast connections to the X server, everything running on fast
27# computers... does being this careful to avoid sync's actually matter?)
28
29__all__ = ["XError", "trap"]
30
31import time
32import os
33
34#run xpra in synchronized mode to debug X11 errors:
35XPRA_SYNCHRONIZE = os.environ.get("XPRA_SYNCHRONIZE", "1")=="1"
36#useful for debugging X11 errors that get swallowed:
37XPRA_SYNC_DEBUG = os.environ.get("XPRA_SYNC_DEBUG", "0")!="0"
38XPRA_TRACE_DEFAULTS = True
39XPRA_LOG_ALL = False
40XPRA_TRACE_ALL = False
41XPRA_FORCE_UNSYNCED = False
42XPRA_FORCE_SYNCED = True
43
44import gtk.gdk
45
46from wimpiggy.log import Logger
47log = Logger()
48
49class XError(Exception):
50    def __init__(self, message):
51        Exception.__init__(self)
52        self.msg = message
53
54    def __str__(self):
55        return "XError: %s" % str(self.msg)
56
57
58xerror_to_name = None
59def XErrorToName(xerror):
60    global xerror_to_name
61    if type(xerror)!=int:
62        return xerror
63    try:
64        from wimpiggy.lowlevel import const, get_error_text     #@UnresolvedImport
65        if xerror_to_name is None:
66            xerror_to_name = {}
67            for name,code in const.items():
68                if name=="Success" or name.startswith("Bad"):
69                    xerror_to_name[code] = name
70            log("XErrorToName(..) initialized error names: %s", xerror_to_name)
71        if xerror in xerror_to_name:
72            return xerror_to_name.get(xerror)
73        return get_error_text(xerror)
74    except Exception, e:
75        log.error("XErrorToName: %s", e, exc_info=True)
76    return xerror
77
78# gdk has its own depth tracking stuff, but we have to duplicate it here to
79# minimize calls to XSync.
80class _ErrorManager(object):
81    def __init__(self):
82        self.depth = 0
83        if XPRA_SYNCHRONIZE:
84            self.do_call = self.call_synced
85            self.do_swallow = self.swallow_synced
86        else:
87            self.do_call = self.call_unsynced
88            self.do_swallow = self.swallow_unsynced
89        if XPRA_TRACE_DEFAULTS:
90            self.call = self.trace_call
91            self.swallow = self.trace_swallow
92        else:
93            self.call = self.do_call
94            self.swallow = self.do_swallow
95        if XPRA_FORCE_SYNCED:
96            self.call_unsynced = self.call_synced
97            self.swallow_unsynced = self.swallow_synced
98        elif XPRA_FORCE_UNSYNCED:
99            self.call_synced = self.call_unsynced
100            self.swallow_synced = self.swallow_unsynced
101
102    def _enter(self):
103        assert self.depth >= 0
104        gtk.gdk.error_trap_push()
105        self.depth += 1
106
107    def _exit(self, need_sync):
108        assert self.depth >= 0
109        self.depth -= 1
110        if self.depth == 0 and need_sync:
111            gtk.gdk.flush()
112        # This is a Xlib error constant (Success == 0)
113        error = gtk.gdk.error_trap_pop()
114        if error:
115            raise XError(XErrorToName(error))
116
117    def _call(self, need_sync, fun, args, kwargs):
118        start = time.time()
119        if XPRA_TRACE_ALL:
120            log.info("call(%s, %s, %s, %s)", need_sync, fun, args, kwargs)
121            import traceback
122            traceback.print_stack()
123        # Goal: call the function.  In all conditions, call _exit exactly once
124        # on the way out.  However, if we are exiting because of an exception,
125        # then probably that exception is more informative than any XError
126        # that might also be raised, so suppress the XError in that case.
127        value = None
128        try:
129            self._enter()
130            value = fun(*args, **kwargs)
131        except Exception, e:
132            einfo = e
133            if type(e)==XError:
134                einfo = XErrorToName(e.msg)
135            if XPRA_SYNC_DEBUG:
136                log.error("_call(%s,%s,%s,%s) %s", need_sync, fun, args, kwargs, einfo, exc_info=True)
137            else:
138                log("_call(%s,%s,%s,%s) %s", need_sync, fun, args, kwargs, einfo)
139            try:
140                self._exit(need_sync)
141            except XError, ee:
142                log("XError %s detected while already in unwind for %s; discarding", XErrorToName(ee), einfo)
143            raise e
144        self._exit(need_sync)
145        if XPRA_LOG_ALL:
146            log.info("call(%s, %s, %s, %s) took %s ms", need_sync, fun, args, kwargs, int(10*(time.time()-start))/10.0)
147        return value
148
149    def call_unsynced(self, fun, *args, **kwargs):
150        return self._call(False, fun, args, kwargs)
151
152    def call_synced(self, fun, *args, **kwargs):
153        return self._call(True, fun, args, kwargs)
154
155    def trace_call(self, fun, *args, **kwargs):
156        log.info("%s(%s, %s)", fun, args, kwargs)
157        import traceback
158        traceback.print_stack()
159        self.do_call(fun, *args, **kwargs)
160
161    def swallow_unsynced(self, fun, *args, **kwargs):
162        try:
163            self.call_unsynced(fun, *args, **kwargs)
164            return True
165        except XError, e:
166            log("Ignoring X error: %s on %s", XErrorToName(e.msg), fun)
167            return False
168
169    def swallow_synced(self, fun, *args, **kwargs):
170        try:
171            self.call_synced(fun, *args, **kwargs)
172            return True
173        except XError, e:
174            log("Ignoring X error: %s on %s", XErrorToName(e.msg), fun)
175            return False
176
177    def trace_swallow(self, fun, *args, **kwargs):
178        log.info("%s(%s, %s)", fun, args, kwargs)
179        import traceback
180        traceback.print_stack()
181        self.do_swallow(fun, *args, **kwargs)
182
183
184    def assert_out(self):
185        assert self.depth == 0
186
187trap = _ErrorManager()