DebugClients/Python/DebugClientThreads.py

branch
debugger speed
changeset 5178
878ce843ca9f
parent 5162
bbf2bb2d533c
equal deleted inserted replaced
5174:8c48f5e0cd92 5178:878ce843ca9f
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the multithreaded version of the debug client.
8 """
9
10 import _thread
11 import sys
12
13 from DebugThread import DebugThread
14 import DebugClientBase
15
16
17 def _debugclient_start_new_thread(target, args, kwargs={}):
18 """
19 Module function used to allow for debugging of multiple threads.
20
21 The way it works is that below, we reset _thread._start_new_thread to
22 this function object. Thus, providing a hook for us to see when
23 threads are started. From here we forward the request onto the
24 DebugClient which will create a DebugThread object to allow tracing
25 of the thread then start up the thread. These actions are always
26 performed in order to allow dropping into debug mode.
27
28 See DebugClientThreads.attachThread and DebugThread.DebugThread in
29 DebugThread.py
30
31 @param target the start function of the target thread (i.e. the user code)
32 @param args arguments to pass to target
33 @param kwargs keyword arguments to pass to target
34 @return The identifier of the created thread
35 """
36 if DebugClientBase.DebugClientInstance is not None:
37 return DebugClientBase.DebugClientInstance.attachThread(
38 target, args, kwargs)
39 else:
40 return _original_start_thread(target, args, kwargs)
41
42 # make _thread hooks available to system
43 _original_start_thread = _thread.start_new_thread
44 _thread.start_new_thread = _debugclient_start_new_thread
45
46 # Note: import threading here AFTER above hook, as threading cache's
47 # thread._start_new_thread.
48 from threading import RLock
49
50
51 class DebugClientThreads(DebugClientBase.DebugClientBase):
52 """
53 Class implementing the client side of the debugger.
54
55 This variant of the debugger implements a threaded debugger client
56 by subclassing all relevant base classes.
57 """
58 def __init__(self):
59 """
60 Constructor
61 """
62 DebugClientBase.DebugClientBase.__init__(self)
63
64 # protection lock for synchronization
65 self.clientLock = RLock()
66
67 # the "current" thread, basically the thread we are at a breakpoint
68 # for.
69 self.currentThread = None
70
71 # special objects representing the main scripts thread and frame
72 self.mainThread = None
73 self.mainFrame = None
74
75 self.variant = 'Threaded'
76
77 def attachThread(self, target=None, args=None, kwargs=None,
78 mainThread=False):
79 """
80 Public method to setup a thread for DebugClient to debug.
81
82 If mainThread is True, then we are attaching to the already
83 started mainthread of the app and the rest of the args are ignored.
84
85 @param target the start function of the target thread (i.e. the
86 user code)
87 @param args arguments to pass to target
88 @param kwargs keyword arguments to pass to target
89 @param mainThread True, if we are attaching to the already
90 started mainthread of the app
91 @return identifier of the created thread
92 """
93 try:
94 self.lockClient()
95 newThread = DebugThread(self, target, args, kwargs, mainThread)
96 ident = -1
97 if mainThread:
98 ident = _thread.get_ident()
99 self.mainThread = newThread
100 if self.debugging:
101 sys.setprofile(newThread.profile)
102 else:
103 ident = _original_start_thread(newThread.bootstrap, ())
104 if self.mainThread is not None:
105 self.tracePython = self.mainThread.tracePython
106 newThread.set_ident(ident)
107 self.threads[newThread.get_ident()] = newThread
108 finally:
109 self.unlockClient()
110 return ident
111
112 def threadTerminated(self, dbgThread):
113 """
114 Public method called when a DebugThread has exited.
115
116 @param dbgThread the DebugThread that has exited
117 """
118 self.lockClient()
119 try:
120 del self.threads[dbgThread.get_ident()]
121 except KeyError:
122 pass
123 finally:
124 self.unlockClient()
125
126 def lockClient(self, blocking=True):
127 """
128 Public method to acquire the lock for this client.
129
130 @param blocking flag to indicating a blocking lock
131 @type bool
132 @return flag indicating successful locking
133 @rtype bool
134 """
135 if blocking:
136 self.clientLock.acquire()
137 else:
138 return self.clientLock.acquire(blocking)
139
140 def unlockClient(self):
141 """
142 Public method to release the lock for this client.
143 """
144 try:
145 self.clientLock.release()
146 except AssertionError:
147 pass
148
149 def setCurrentThread(self, id):
150 """
151 Public method to set the current thread.
152
153 @param id the id the current thread should be set to.
154 """
155 try:
156 self.lockClient()
157 if id is None or id not in self.threads:
158 self.currentThread = None
159 else:
160 self.currentThread = self.threads[id]
161 finally:
162 self.unlockClient()
163
164 def eventLoop(self, disablePolling=False):
165 """
166 Public method implementing our event loop.
167
168 @param disablePolling flag indicating to enter an event loop with
169 polling disabled (boolean)
170 """
171 # make sure we set the current thread appropriately
172 threadid = _thread.get_ident()
173 self.setCurrentThread(threadid)
174
175 DebugClientBase.DebugClientBase.eventLoop(self, disablePolling)
176
177 self.setCurrentThread(None)
178
179 def set_quit(self):
180 """
181 Public method to do a 'set quit' on all threads.
182 """
183 try:
184 locked = self.lockClient(False)
185 try:
186 for key in self.threads:
187 self.threads[key].set_quit()
188 except Exception:
189 pass
190 finally:
191 if locked:
192 self.unlockClient()
193
194 # We are normally called by the debugger to execute directly.
195
196 if __name__ == '__main__':
197 debugClient = DebugClientThreads()
198 debugClient.main()
199
200 #
201 # eflag: noqa = M702, E402

eric ide

mercurial