Hide keyboard shortcuts

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. 

5 

6import threading 

7from threading import Event 

8 

9from xpra.make_thread import start_thread 

10from xpra.os_util import monotonic_time 

11from xpra.util import envint 

12from xpra.log import Logger 

13 

14log = Logger("util") 

15 

16FAKE_UI_LOCKUPS = envint("XPRA_FAKE_UI_LOCKUPS") 

17POLLING = envint("XPRA_UI_THREAD_POLLING", 500) 

18 

19 

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() 

37 

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() 

46 

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) 

66 

67 def stop(self): 

68 self.exit.set() 

69 

70 def add_fail_callback(self, cb): 

71 self.fail_callbacks.append(cb) 

72 

73 def add_resume_callback(self, cb): 

74 self.resume_callbacks.append(cb) 

75 

76 def add_alive_callback(self, cb): 

77 self.alive_callbacks.append(cb) 

78 

79 

80 def remove_fail_callback(self, cb): 

81 self.fail_callbacks.remove(cb) 

82 

83 def remove_resume_callback(self, cb): 

84 self.resume_callbacks.remove(cb) 

85 

86 def remove_alive_callback(self, cb): 

87 self.alive_callbacks.remove(cb) 

88 

89 

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) 

96 

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 

111 

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) 

153 

154 

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