Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/gtk_common/error.py : 68%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of Xpra.
2# Copyright (C) 2008, 2009 Nathaniel Smith <njs@pobox.com>
3# Copyright (C) 2012-2019 Antoine Martin <antoine@xpra.org>
4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
5# later version. See the file COPYING for details.
7# Goal: make it as easy and efficient as possible to manage the X errors that
8# a WM is inevitably susceptible to. (E.g., if a window goes away while we
9# are working on it.) On the one hand, we want to parcel operations into as
10# broad chunks as possible that at treated as succeeding or failing as a whole
11# (e.g., "setting up a new window", we don't really care how much was
12# accomplished before the failure occurred). On the other, we do want to
13# check for X errors often, for use in debugging (esp., this makes it more
14# useful to run with -sync).
15#
16# The solution is to keep a stack of how deep we are in "transaction-like"
17# operations -- a transaction is a series of operations where we don't care if
18# we don't find about the failures until the end. We only sync when exiting a
19# top-level transaction.
20#
21# The _synced and _unsynced variants differ in whether they assume the X
22# connection was left in a synchronized state by the code they called (e.g.,
23# if the last operation was an XGetProperty, then there is no need for us to
24# do another XSync).
25#
26# (In this modern world, with WM's either on the same machine or over
27# super-fast connections to the X server, everything running on fast
28# computers... does being this careful to avoid sync's actually matter?)
30import traceback
31from gi.repository import Gdk
33from xpra.util import envbool
34from xpra.os_util import is_main_thread
35from xpra.log import Logger
37__all__ = ["XError", "trap", "xsync", "xswallow"]
39#run xpra in synchronized mode to debug X11 errors:
40XPRA_SYNCHRONIZE = envbool("XPRA_SYNCHRONIZE", False)
41XPRA_LOG_SYNC = envbool("XPRA_LOG_SYNC", False)
42VERIFY_MAIN_THREAD = envbool("XPRA_VERIFY_MAIN_THREAD", True)
43LOG_NESTED_XTRAP = envbool("XPRA_LOG_NESTED_XTRAP", False)
45log = Logger("x11", "util")
46elog = Logger("x11", "util", "error")
49if not VERIFY_MAIN_THREAD:
50 def verify_main_thread():
51 return
52else:
53 def verify_main_thread():
54 if not is_main_thread():
55 import threading
56 log.error("Error: invalid access from thread %s", threading.current_thread())
57 traceback.print_stack()
58 verify_main_thread()
61class XError(Exception):
62 def __init__(self, message):
63 Exception.__init__(self)
64 self.msg = get_X_error(message)
66 def __str__(self):
67 return "XError: %s" % self.msg
70xerror_to_name = None
71def get_X_error(xerror):
72 global xerror_to_name
73 if not isinstance(xerror, int):
74 return xerror
75 try:
76 from xpra.x11.bindings.window_bindings import constants #@UnresolvedImport
77 if xerror_to_name is None:
78 xerror_to_name = {}
79 for name,code in constants.items(): # @UndefinedVariable
80 if name=="Success" or name.startswith("Bad"):
81 xerror_to_name[code] = name
82 log("get_X_error(..) initialized error names: %s", xerror_to_name)
83 if xerror in xerror_to_name:
84 return xerror_to_name.get(xerror)
85 from xpra.x11.bindings.core_bindings import X11CoreBindings #@UnresolvedImport
86 return X11CoreBindings().get_error_text(xerror)
87 except Exception as e:
88 log.error("get_X_error(%s) %s", xerror, e, exc_info=True)
89 return xerror
92# gdk has its own depth tracking stuff, but we have to duplicate it here to
93# minimize calls to XSync.
94class _ErrorManager:
95 def __init__(self):
96 self.depth = 0
98 def Xenter(self):
99 assert self.depth >= 0
100 verify_main_thread()
101 Gdk.error_trap_push()
102 if XPRA_LOG_SYNC:
103 log("X11trap.enter at level %i", self.depth)
104 if LOG_NESTED_XTRAP and self.depth>0:
105 for x in traceback.extract_stack():
106 log("%s", x)
107 self.depth += 1
109 def Xexit(self, need_sync=True):
110 assert self.depth >= 0
111 self.depth -= 1
112 if XPRA_LOG_SYNC:
113 log("X11trap.exit at level %i, need_sync=%s", self.depth, need_sync)
114 if self.depth == 0 and need_sync:
115 Gdk.flush()
116 # This is a Xlib error constant (Success == 0)
117 error = Gdk.error_trap_pop()
118 if error:
119 raise XError(error)
121 def _call(self, need_sync, fun, args, kwargs):
122 # Goal: call the function. In all conditions, call _exit exactly once
123 # on the way out. However, if we are exiting because of an exception,
124 # then probably that exception is more informative than any XError
125 # that might also be raised, so suppress the XError in that case.
126 value = None
127 try:
128 self.Xenter()
129 value = fun(*args, **kwargs)
130 except Exception as e:
131 elog("_call%s", (need_sync, fun, args, kwargs), exc_info=True)
132 log("_call%s %s", (need_sync, fun, args, kwargs), e)
133 try:
134 self.Xexit(need_sync)
135 except XError as ee:
136 log("XError %s detected while already in unwind; discarding",
137 ee, exc_info=True)
138 raise
139 self.Xexit(need_sync)
140 return value
142 def call_unsynced(self, fun, *args, **kwargs):
143 return self._call(False, fun, args, kwargs)
145 def call_synced(self, fun, *args, **kwargs):
146 return self._call(True, fun, args, kwargs)
148 if XPRA_SYNCHRONIZE:
149 call = call_synced
150 else:
151 call = call_unsynced
153 def swallow_unsynced(self, fun, *args, **kwargs):
154 try:
155 self.call_unsynced(fun, *args, **kwargs)
156 return True
157 except XError:
158 log("Ignoring X error on %s",
159 fun, exc_info=True)
160 return False
162 def swallow_synced(self, fun, *args, **kwargs):
163 try:
164 self.call_synced(fun, *args, **kwargs)
165 return True
166 except XError:
167 log("Ignoring X error on %s",
168 fun, exc_info=True)
169 return False
171 if XPRA_SYNCHRONIZE:
172 swallow = swallow_synced
173 else:
174 swallow = swallow_unsynced
176 def assert_out(self):
177 assert self.depth == 0
179trap = _ErrorManager()
182class XSyncContext:
184 def __enter__(self):
185 trap.Xenter()
187 def __exit__(self, e_typ, _e_val, _trcbak):
188 #log("xsync.exit%s", (e_typ, e_val, trcbak))
189 try:
190 trap.Xexit()
191 except XError:
192 if e_typ is None:
193 #we are not handling an exception yet, so raise this one:
194 raise
195 log("XError detected while already in unwind; discarding",
196 exc_info=True)
197 #raise the original exception:
198 return False
200xsync = XSyncContext()
203class XSwallowContext:
205 def __enter__(self):
206 trap.Xenter()
208 def __exit__(self, e_typ, e_val, trcbak):
209 if e_typ:
210 log("xswallow.exit%s", (e_typ, e_val, trcbak), exc_info=True)
211 try:
212 trap.Xexit()
213 except XError:
214 log("XError detected while already in unwind; discarding",
215 exc_info=True)
216 #don't raise exceptions:
217 return True
219xswallow = XSwallowContext()
222class XLogContext:
224 def __enter__(self):
225 trap.Xenter()
227 def __exit__(self, e_typ, e_val, trcbak):
228 if e_typ:
229 log.error("XError: %s, %s", e_typ, e_val, exc_info=True)
230 try:
231 trap.Xexit()
232 except XError:
233 log("XError detected while already in unwind; discarding",
234 exc_info=True)
235 #don't raise exceptions:
236 return True
238xlog = XLogContext()
241def verify_sync():
242 if trap.depth<=0:
243 log.error("Error: unmanaged X11 context")
244 stack = traceback.extract_stack()[:-1]
245 s = traceback.format_list(stack)
246 for x in s:
247 for v in x.splitlines():
248 log.error(" %s", v)
249 #raise Exception("unmanaged context")