Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/platform/ui_thread_watcher.py : 70%
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) 2013-2016 Antoine Martin <antoine@xpra.org>
3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
4# later version. See the file COPYING for details.
6import threading
7from threading import Event
9from xpra.make_thread import start_thread
10from xpra.os_util import monotonic_time
11from xpra.util import envint
12from xpra.log import Logger
14log = Logger("util")
16FAKE_UI_LOCKUPS = envint("XPRA_FAKE_UI_LOCKUPS")
17POLLING = envint("XPRA_UI_THREAD_POLLING", 500)
20class UI_thread_watcher:
21 """
22 Allows us to register callbacks
23 to fire when the UI thread fails to run
24 or when it resumes.
25 We run a dedicated thread to verify that
26 the UI thread has run since the last time it was
27 scheduled to run.
28 Beware that the callbacks (fail, resume and alive)
29 will run from different threads..
30 """
31 def __init__(self, timeout_add, source_remove, polling_timeout):
32 self.timeout_add = timeout_add
33 self.source_remove = source_remove
34 self.polling_timeout = polling_timeout
35 self.max_delta = polling_timeout * 2
36 self.init_vars()
38 def init_vars(self):
39 self.alive_callbacks = []
40 self.fail_callbacks = []
41 self.resume_callbacks = []
42 self.UI_blocked = False
43 self.last_UI_thread_time = 0
44 self.ui_wakeup_timer = None
45 self.exit = Event()
47 def start(self):
48 if self.last_UI_thread_time>0:
49 log.warn("UI thread watcher already started!")
50 return
51 #run once to initialize:
52 self.UI_thread_wakeup()
53 if self.polling_timeout>0:
54 start_thread(self.poll_UI_loop, "UI thread polling", daemon=True)
55 else:
56 log("not starting an IO polling thread")
57 if FAKE_UI_LOCKUPS>0:
58 #watch out: sleeping in UI thread!
59 def sleep_in_ui_thread(*args):
60 t = threading.current_thread()
61 log.warn("sleep_in_ui_thread%s pausing %s for %ims", args, t, FAKE_UI_LOCKUPS)
62 import time
63 time.sleep(FAKE_UI_LOCKUPS/1000.0)
64 return True
65 self.timeout_add(10*1000+FAKE_UI_LOCKUPS, sleep_in_ui_thread)
67 def stop(self):
68 self.exit.set()
70 def add_fail_callback(self, cb):
71 self.fail_callbacks.append(cb)
73 def add_resume_callback(self, cb):
74 self.resume_callbacks.append(cb)
76 def add_alive_callback(self, cb):
77 self.alive_callbacks.append(cb)
80 def remove_fail_callback(self, cb):
81 self.fail_callbacks.remove(cb)
83 def remove_resume_callback(self, cb):
84 self.resume_callbacks.remove(cb)
86 def remove_alive_callback(self, cb):
87 self.alive_callbacks.remove(cb)
90 def run_callbacks(self, callbacks):
91 for x in callbacks:
92 try:
93 x()
94 except Exception:
95 log.error("failed to run %s", x, exc_info=True)
97 def UI_thread_wakeup(self, scheduled_at=0):
98 if scheduled_at:
99 elapsed = monotonic_time()-scheduled_at
100 else:
101 elapsed = 0
102 self.ui_wakeup_timer = None
103 log("UI_thread_wakeup() elapsed=%.2fms", 1000*elapsed)
104 self.last_UI_thread_time = monotonic_time()
105 #UI thread was blocked?
106 if self.UI_blocked:
107 log.info("UI thread is running again, resuming")
108 self.UI_blocked = False
109 self.run_callbacks(self.resume_callbacks)
110 return False
112 def poll_UI_loop(self):
113 log("poll_UI_loop() running")
114 while not self.exit.isSet():
115 delta = monotonic_time()-self.last_UI_thread_time
116 log("poll_UI_loop() last_UI_thread_time was %.1f seconds ago (max %i), UI_blocked=%s",
117 delta, self.max_delta/1000, self.UI_blocked)
118 if delta>self.max_delta/1000.0:
119 #UI thread is (still?) blocked:
120 if not self.UI_blocked:
121 log.info("UI thread is now blocked")
122 self.UI_blocked = True
123 self.run_callbacks(self.fail_callbacks)
124 else:
125 #seems to be ok:
126 log("poll_UI_loop() ok, firing %s", self.alive_callbacks)
127 self.run_callbacks(self.alive_callbacks)
128 now = monotonic_time()
129 self.ui_wakeup_timer = self.timeout_add(0, self.UI_thread_wakeup, now)
130 wstart = monotonic_time()
131 wait_time = self.polling_timeout/1000.0 #convert to seconds
132 self.exit.wait(wait_time)
133 if not self.exit.isSet():
134 wdelta = monotonic_time() - wstart
135 log("wait(%.4f) actually waited %.4f", self.polling_timeout/1000.0, wdelta)
136 if wdelta>(wait_time+1):
137 #this can be caused by suspend + resume
138 if wdelta>60 and not self.UI_blocked:
139 log.info("no service for %i seconds", wdelta)
140 else:
141 log.warn("Warning: long timer waiting time,")
142 log.warn(" UI thread polling waited %.1f seconds longer than intended (%.1f vs %.1f)",
143 wdelta-wait_time, wdelta, wait_time)
144 #force run resume (even if we never fired the fail callbacks)
145 self.UI_blocked = False
146 self.UI_thread_wakeup()
147 self.init_vars()
148 log("poll_UI_loop() ended")
149 uiwt = self.ui_wakeup_timer
150 if uiwt:
151 self.ui_wakeup_timer = None
152 self.source_remove(uiwt)
155UI_watcher = None
156def get_UI_watcher(timeout_add=None, source_remove=None):
157 global UI_watcher
158 if UI_watcher is None and timeout_add:
159 UI_watcher = UI_thread_watcher(timeout_add, source_remove, POLLING)
160 log("get_UI_watcher(%s)", timeout_add)
161 return UI_watcher