~stomato463/+junk/nvdajp

« back to all changes in this revision

Viewing changes to source/watchdog.py

  • Committer: Masataka Shinke
  • Date: 2011-10-25 12:35:26 UTC
  • mfrom: (4185 jpmain)
  • mto: This revision was merged to the branch mainline in revision 4211.
  • Revision ID: mshinke@users.sourceforge.jp-20111025123526-ze527a2rl3z0g2ky
lp:~nishimotz/nvdajp/main : 4185 をマージ

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#watchdog.py
 
2
#A part of NonVisual Desktop Access (NVDA)
 
3
#Copyright (C) 2008-2011 NV Access Inc
 
4
#This file is covered by the GNU General Public License.
 
5
#See the file COPYING for more details.
 
6
 
 
7
import sys
 
8
import traceback
1
9
import time
2
10
import threading
3
 
from ctypes import *
 
11
import inspect
 
12
from ctypes import windll, oledll
 
13
import ctypes.wintypes
 
14
import comtypes
4
15
import winUser
5
 
import api
 
16
import winKernel
 
17
from logHandler import log
6
18
 
7
19
#settings
8
20
#: How often to check whether the core is alive
10
22
#: How long to wait for the core to be alive under normal circumstances
11
23
NORMAL_CORE_ALIVE_TIMEOUT=10
12
24
#: The minimum time to wait for the core to be alive
13
 
MIN_CORE_ALIVE_TIMEOUT=0.3
 
25
MIN_CORE_ALIVE_TIMEOUT=0.5
14
26
#: How long to wait between recovery attempts
15
27
RECOVER_ATTEMPT_INTERVAL = 0.05
 
28
#: The amount of time before the core should be considered severely frozen and a warning logged.
 
29
FROZEN_WARNING_TIMEOUT = 15
16
30
 
17
31
safeWindowClassSet=set([
18
32
        'Internet Explorer_Server',
19
33
        '_WwG',
 
34
        'EXCEL7',
20
35
])
21
36
 
22
37
isRunning=False
27
42
_coreThreadID=windll.kernel32.GetCurrentThreadId()
28
43
_watcherThread=None
29
44
 
 
45
class CallCancelled(Exception):
 
46
        """Raised when a call is cancelled.
 
47
        """
 
48
 
30
49
def alive():
31
50
        """Inform the watchdog that the core is alive.
32
51
        """
52
71
                        waited += timeout
53
72
                        if _coreAliveEvent.isSet() or _shouldRecoverAfterMinTimeout():
54
73
                                break
55
 
 
 
74
                if log.isEnabledFor(log.DEBUGWARNING) and not _coreAliveEvent.isSet():
 
75
                        log.debugWarning("Trying to recover from freeze, core stack:\n%s"%
 
76
                                "".join(traceback.format_stack(sys._current_frames()[_coreThreadID])))
 
77
                lastTime=time.time()
56
78
                while not _coreAliveEvent.isSet():
 
79
                        curTime=time.time()
 
80
                        if curTime-lastTime>FROZEN_WARNING_TIMEOUT:
 
81
                                lastTime=curTime
 
82
                                log.warning("Core frozen in stack:\n%s"%
 
83
                                        "".join(traceback.format_stack(sys._current_frames()[_coreThreadID])))
57
84
                        # The core is dead, so attempt recovery.
58
85
                        isAttemptingRecovery = True
59
86
                        _recoverAttempt()
67
94
 
68
95
def _shouldRecoverAfterMinTimeout():
69
96
        info=winUser.getGUIThreadInfo(0)
 
97
        if not info.hwndFocus:
 
98
                # The foreground thread is frozen or there is no foreground thread (probably due to a freeze elsewhere).
 
99
                return True
 
100
        # Import late to avoid circular import.
 
101
        import api
 
102
        #If a system menu has been activated but NVDA's focus is not yet in the menu then use min timeout
 
103
        if info.flags&winUser.GUI_SYSTEMMENUMODE and info.hwndMenuOwner and api.getFocusObject().windowClassName!='#32768':
 
104
                return True 
70
105
        if winUser.getClassName(info.hwndFocus) in safeWindowClassSet:
71
106
                return False
72
107
        if not winUser.isDescendantWindow(info.hwndActive, api.getFocusObject().windowHandle):
73
108
                # The foreground window has changed.
74
109
                return True
75
 
        newHwnd=info.hwndFocus if info.hwndFocus else info.hwndActive
 
110
        newHwnd=info.hwndFocus
76
111
        newThreadID=winUser.getWindowThreadProcessID(newHwnd)[1]
77
112
        return newThreadID!=api.getFocusObject().windowThreadID
78
113
 
81
116
                oledll.ole32.CoCancelCall(_coreThreadID,0)
82
117
        except:
83
118
                pass
 
119
        import NVDAHelper
 
120
        NVDAHelper.localLib.cancelSendMessage()
 
121
 
 
122
@ctypes.WINFUNCTYPE(ctypes.wintypes.LONG, ctypes.c_void_p)
 
123
def _crashHandler(exceptionInfo):
 
124
        # An exception might have been set for this thread.
 
125
        # Clear it so that it doesn't get raised in this function.
 
126
        ctypes.pythonapi.PyThreadState_SetAsyncExc(threading.currentThread().ident, None)
 
127
        import core
 
128
        core.restart()
 
129
        return 1 # EXCEPTION_EXECUTE_HANDLER
 
130
 
 
131
@ctypes.WINFUNCTYPE(None)
 
132
def _notifySendMessageCancelled():
 
133
        caller = inspect.currentframe().f_back
 
134
        if not caller:
 
135
                return
 
136
        # Set a profile function which will raise an exception when returning from the calling frame.
 
137
        def sendMessageCallCanceller(frame, event, arg):
 
138
                if frame == caller:
 
139
                        # Raising an exception will also cause the profile function to be deactivated.
 
140
                        raise CallCancelled
 
141
        sys.setprofile(sendMessageCallCanceller)
 
142
 
 
143
RPC_E_CALL_CANCELED = -2147418110
 
144
_orig_COMError_init = comtypes.COMError.__init__
 
145
def _COMError_init(self, hresult, text, details):
 
146
        if hresult == RPC_E_CALL_CANCELED:
 
147
                raise CallCancelled
 
148
        _orig_COMError_init(self, hresult, text, details)
84
149
 
85
150
def initialize():
86
151
        """Initialize the watchdog.
89
154
        if isRunning:
90
155
                raise RuntimeError("already running") 
91
156
        isRunning=True
 
157
        # Catch application crashes.
 
158
        windll.kernel32.SetUnhandledExceptionFilter(_crashHandler)
92
159
        oledll.ole32.CoEnableCallCancellation(None)
 
160
        # Handle cancelled SendMessage calls.
 
161
        import NVDAHelper
 
162
        NVDAHelper._setDllFuncPointer(NVDAHelper.localLib, "_notifySendMessageCancelled", _notifySendMessageCancelled)
 
163
        # Monkey patch comtypes to specially handle cancelled COM calls.
 
164
        comtypes.COMError.__init__ = _COMError_init
93
165
        _coreAliveEvent.set()
94
166
        _resumeEvent.set()
95
167
        _watcherThread=threading.Thread(target=_watcher)
103
175
                return
104
176
        isRunning=False
105
177
        oledll.ole32.CoDisableCallCancellation(None)
 
178
        comtypes.COMError.__init__ = _orig_COMError_init
106
179
        _resumeEvent.set()
107
180
        _coreAliveEvent.set()
108
181
        _watcherThread.join()
116
189
 
117
190
        def __exit__(self,*args):
118
191
                _resumeEvent.set()
 
192
 
 
193
class CancellableCallThread(threading.Thread):
 
194
        """A worker thread used to execute a call which must be made cancellable.
 
195
        If the call is cancelled, this thread must be abandoned.
 
196
        """
 
197
 
 
198
        def __init__(self):
 
199
                super(CancellableCallThread, self).__init__()
 
200
                self.daemon = True
 
201
                self._executeEvent = threading.Event()
 
202
                self._executionDoneEvent = ctypes.windll.kernel32.CreateEventW(None, False, False, None)
 
203
                self.isUsable = True
 
204
 
 
205
        def execute(self, func, args, kwargs, pumpMessages=True):
 
206
                # Don't even bother making the call if the core is already dead.
 
207
                if isAttemptingRecovery:
 
208
                        raise CallCancelled
 
209
 
 
210
                self._func = func
 
211
                self._args = args
 
212
                self._kwargs = kwargs
 
213
                self._result = None
 
214
                self._exc_info = None
 
215
                self._executeEvent.set()
 
216
 
 
217
                if pumpMessages:
 
218
                        waitHandles = (ctypes.wintypes.HANDLE * 1)(self._executionDoneEvent)
 
219
                        waitIndex = ctypes.wintypes.DWORD()
 
220
                timeout = int(1000 * CHECK_INTERVAL)
 
221
                while True:
 
222
                        if pumpMessages:
 
223
                                try:
 
224
                                        oledll.ole32.CoWaitForMultipleHandles(0, timeout, 1, waitHandles, ctypes.byref(waitIndex))
 
225
                                        break
 
226
                                except WindowsError:
 
227
                                        pass
 
228
                        else:
 
229
                                if windll.kernel32.WaitForSingleObject(self._executionDoneEvent, timeout) != winKernel.WAIT_TIMEOUT:
 
230
                                        break
 
231
                        if isAttemptingRecovery:
 
232
                                self.isUsable = False
 
233
                                raise CallCancelled
 
234
 
 
235
                exc = self._exc_info
 
236
                if exc:
 
237
                        raise exc[0], exc[1], exc[2]
 
238
                return self._result
 
239
 
 
240
        def run(self):
 
241
                comtypes.CoInitializeEx(comtypes.COINIT_MULTITHREADED)
 
242
                while self.isUsable:
 
243
                        self._executeEvent.wait()
 
244
                        self._executeEvent.clear()
 
245
                        try:
 
246
                                self._result = self._func(*self._args, **self._kwargs)
 
247
                        except:
 
248
                                self._exc_info = sys.exc_info()
 
249
                        ctypes.windll.kernel32.SetEvent(self._executionDoneEvent)
 
250
                ctypes.windll.kernel32.CloseHandle(self._executionDoneEvent)
 
251
 
 
252
cancellableCallThread = None
 
253
def cancellableExecute(func, *args, **kwargs):
 
254
        """Execute a function in the main thread, making it cancellable.
 
255
        @param func: The function to execute.
 
256
        @type func: callable
 
257
        @param ccPumpMessages: Whether to pump messages while waiting.
 
258
        @type ccPumpMessages: bool
 
259
        @param args: Positional arguments for the function.
 
260
        @param kwargs: Keyword arguments for the function.
 
261
        @raise CallCancelled: If the call was cancelled.
 
262
        """
 
263
        global cancellableCallThread
 
264
        pumpMessages = kwargs.pop("ccPumpMessages", True)
 
265
        if not isRunning or not _resumeEvent.isSet() or not isinstance(threading.currentThread(), threading._MainThread):
 
266
                # Watchdog is not running or this is a background thread,
 
267
                # so just execute the call.
 
268
                return func(*args, **kwargs)
 
269
        if not cancellableCallThread or not cancellableCallThread.isUsable:
 
270
                # The thread hasn't yet been created or is not usable.
 
271
                # Create a new one.
 
272
                cancellableCallThread = CancellableCallThread()
 
273
                cancellableCallThread.start()
 
274
        return cancellableCallThread.execute(func, args, kwargs, pumpMessages=pumpMessages)
 
275
 
 
276
def cancellableSendMessage(hwnd, msg, wParam, lParam, flags=0, timeout=60000):
 
277
        """Send a window message, making the call cancellable.
 
278
        The C{timeout} and C{flags} arguments should usually be left at their default values.
 
279
        The call will still be cancelled if appropriate even if the specified timeout has not yet been reached.
 
280
        @raise CallCancelled: If the call was cancelled.
 
281
        """
 
282
        import NVDAHelper
 
283
        result = ctypes.wintypes.DWORD()
 
284
        NVDAHelper.localLib.cancellableSendMessageTimeout(hwnd, msg, wParam, lParam, flags, timeout, ctypes.byref(result))
 
285
        return result.value