1 | #!/usr/bin/env python |
---|
2 | # -*- coding: utf-8 -*- |
---|
3 | |
---|
4 | """ |
---|
5 | | This file is part of the web2py Web Framework |
---|
6 | | Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>, |
---|
7 | | limodou <limodou@gmail.com> and srackham <srackham@gmail.com>. |
---|
8 | | License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) |
---|
9 | |
---|
10 | Debugger support classes |
---|
11 | ------------------------ |
---|
12 | """ |
---|
13 | |
---|
14 | import logging |
---|
15 | import pdb |
---|
16 | import sys |
---|
17 | from gluon._compat import Queue |
---|
18 | |
---|
19 | logger = logging.getLogger("web2py") |
---|
20 | |
---|
21 | |
---|
22 | class Pipe(Queue.Queue): |
---|
23 | def __init__(self, name, mode='r', *args, **kwargs): |
---|
24 | self.__name = name |
---|
25 | Queue.Queue.__init__(self, *args, **kwargs) |
---|
26 | |
---|
27 | def write(self, data): |
---|
28 | logger.debug("debug %s writing %s" % (self.__name, data)) |
---|
29 | self.put(data) |
---|
30 | |
---|
31 | def flush(self): |
---|
32 | # mark checkpoint (complete message) |
---|
33 | logger.debug("debug %s flushing..." % self.__name) |
---|
34 | self.put(None) |
---|
35 | # wait until it is processed |
---|
36 | self.join() |
---|
37 | logger.debug("debug %s flush done" % self.__name) |
---|
38 | |
---|
39 | def read(self, count=None, timeout=None): |
---|
40 | logger.debug("debug %s reading..." % (self.__name, )) |
---|
41 | data = self.get(block=True, timeout=timeout) |
---|
42 | # signal that we are ready |
---|
43 | self.task_done() |
---|
44 | logger.debug("debug %s read %s" % (self.__name, data)) |
---|
45 | return data |
---|
46 | |
---|
47 | def readline(self): |
---|
48 | logger.debug("debug %s readline..." % (self.__name, )) |
---|
49 | return self.read() |
---|
50 | |
---|
51 | |
---|
52 | pipe_in = Pipe('in') |
---|
53 | pipe_out = Pipe('out') |
---|
54 | |
---|
55 | debugger = pdb.Pdb(completekey=None, stdin=pipe_in, stdout=pipe_out,) |
---|
56 | |
---|
57 | |
---|
58 | def set_trace(): |
---|
59 | """breakpoint shortcut (like pdb)""" |
---|
60 | logger.info("DEBUG: set_trace!") |
---|
61 | debugger.set_trace(sys._getframe().f_back) |
---|
62 | |
---|
63 | |
---|
64 | def stop_trace(): |
---|
65 | """stop waiting for the debugger (called atexit)""" |
---|
66 | # this should prevent communicate is wait forever a command result |
---|
67 | # and the main thread has finished |
---|
68 | logger.info("DEBUG: stop_trace!") |
---|
69 | pipe_out.write("debug finished!") |
---|
70 | pipe_out.write(None) |
---|
71 | #pipe_out.flush() |
---|
72 | |
---|
73 | |
---|
74 | def communicate(command=None): |
---|
75 | """send command to debbuger, wait result""" |
---|
76 | if command is not None: |
---|
77 | logger.info("DEBUG: sending command %s" % command) |
---|
78 | pipe_in.write(command) |
---|
79 | #pipe_in.flush() |
---|
80 | result = [] |
---|
81 | while True: |
---|
82 | data = pipe_out.read() |
---|
83 | if data is None: |
---|
84 | break |
---|
85 | result.append(data) |
---|
86 | logger.info("DEBUG: result %s" % repr(result)) |
---|
87 | return ''.join(result) |
---|
88 | |
---|
89 | |
---|
90 | # New debugger implementation using dbg and a web UI |
---|
91 | |
---|
92 | import gluon.contrib.dbg as c_dbg |
---|
93 | from threading import RLock |
---|
94 | |
---|
95 | interact_lock = RLock() |
---|
96 | run_lock = RLock() |
---|
97 | |
---|
98 | |
---|
99 | def check_interaction(fn): |
---|
100 | """Decorator to clean and prevent interaction when not available""" |
---|
101 | def check_fn(self, *args, **kwargs): |
---|
102 | interact_lock.acquire() |
---|
103 | try: |
---|
104 | if self.filename: |
---|
105 | self.clear_interaction() |
---|
106 | return fn(self, *args, **kwargs) |
---|
107 | finally: |
---|
108 | interact_lock.release() |
---|
109 | return check_fn |
---|
110 | |
---|
111 | |
---|
112 | class WebDebugger(c_dbg.Frontend): |
---|
113 | """Qdb web2py interface""" |
---|
114 | |
---|
115 | def __init__(self, pipe, completekey='tab', stdin=None, stdout=None): |
---|
116 | c_dbg.Frontend.__init__(self, pipe) |
---|
117 | self.clear_interaction() |
---|
118 | |
---|
119 | def clear_interaction(self): |
---|
120 | self.filename = None |
---|
121 | self.lineno = None |
---|
122 | self.exception_info = None |
---|
123 | self.context = None |
---|
124 | |
---|
125 | # redefine Frontend methods: |
---|
126 | |
---|
127 | def run(self): |
---|
128 | run_lock.acquire() |
---|
129 | try: |
---|
130 | while self.pipe.poll(): |
---|
131 | c_dbg.Frontend.run(self) |
---|
132 | finally: |
---|
133 | run_lock.release() |
---|
134 | |
---|
135 | def interaction(self, filename, lineno, line, **context): |
---|
136 | # store current status |
---|
137 | interact_lock.acquire() |
---|
138 | try: |
---|
139 | self.filename = filename |
---|
140 | self.lineno = lineno |
---|
141 | self.context = context |
---|
142 | finally: |
---|
143 | interact_lock.release() |
---|
144 | |
---|
145 | def exception(self, title, extype, exvalue, trace, request): |
---|
146 | self.exception_info = {'title': title, |
---|
147 | 'extype': extype, 'exvalue': exvalue, |
---|
148 | 'trace': trace, 'request': request} |
---|
149 | |
---|
150 | @check_interaction |
---|
151 | def do_continue(self): |
---|
152 | c_dbg.Frontend.do_continue(self) |
---|
153 | |
---|
154 | @check_interaction |
---|
155 | def do_step(self): |
---|
156 | c_dbg.Frontend.do_step(self) |
---|
157 | |
---|
158 | @check_interaction |
---|
159 | def do_return(self): |
---|
160 | c_dbg.Frontend.do_return(self) |
---|
161 | |
---|
162 | @check_interaction |
---|
163 | def do_next(self): |
---|
164 | c_dbg.Frontend.do_next(self) |
---|
165 | |
---|
166 | @check_interaction |
---|
167 | def do_quit(self): |
---|
168 | c_dbg.Frontend.do_quit(self) |
---|
169 | |
---|
170 | def do_exec(self, statement): |
---|
171 | interact_lock.acquire() |
---|
172 | try: |
---|
173 | # check to see if we're inside interaction |
---|
174 | if self.filename: |
---|
175 | # avoid spurious interaction notifications: |
---|
176 | self.set_burst(2) |
---|
177 | # execute the statement in the remote debugger: |
---|
178 | return c_dbg.Frontend.do_exec(self, statement) |
---|
179 | finally: |
---|
180 | interact_lock.release() |
---|
181 | |
---|
182 | # create the connection between threads: |
---|
183 | |
---|
184 | parent_queue, child_queue = Queue.Queue(), Queue.Queue() |
---|
185 | front_conn = c_dbg.QueuePipe("parent", parent_queue, child_queue) |
---|
186 | child_conn = c_dbg.QueuePipe("child", child_queue, parent_queue) |
---|
187 | |
---|
188 | web_debugger = WebDebugger(front_conn) # frontend |
---|
189 | dbg_debugger = c_dbg.Qdb(pipe=child_conn, redirect_stdio=False, skip=None) # backend |
---|
190 | dbg = dbg_debugger |
---|
191 | |
---|
192 | # enable getting context (stack, globals/locals) at interaction |
---|
193 | dbg_debugger.set_params(dict(call_stack=True, environment=True)) |
---|
194 | |
---|
195 | import gluon.main |
---|
196 | gluon.main.global_settings.debugging = True |
---|