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