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 | |
---|
31 | import time |
---|
32 | import os |
---|
33 | |
---|
34 | #run xpra in synchronized mode to debug X11 errors: |
---|
35 | XPRA_SYNCHRONIZE = os.environ.get("XPRA_SYNCHRONIZE", "1")=="1" |
---|
36 | #useful for debugging X11 errors that get swallowed: |
---|
37 | XPRA_SYNC_DEBUG = os.environ.get("XPRA_SYNC_DEBUG", "0")!="0" |
---|
38 | XPRA_TRACE_DEFAULTS = True |
---|
39 | XPRA_LOG_ALL = False |
---|
40 | XPRA_TRACE_ALL = False |
---|
41 | XPRA_FORCE_UNSYNCED = False |
---|
42 | XPRA_FORCE_SYNCED = True |
---|
43 | |
---|
44 | import gtk.gdk |
---|
45 | |
---|
46 | from wimpiggy.log import Logger |
---|
47 | log = Logger() |
---|
48 | |
---|
49 | class 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 | |
---|
58 | xerror_to_name = None |
---|
59 | def 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. |
---|
80 | class _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 | |
---|
187 | trap = _ErrorManager() |
---|