Mercurial > hg
comparison mercurial/dispatch.py @ 39255:4019b4542e61
dispatch: have dispatch.dispatch and dispatch._runcatch emit trace events
Differential Revision: https://phab.mercurial-scm.org/D4345
author | Augie Fackler <augie@google.com> |
---|---|
date | Tue, 21 Aug 2018 15:25:07 -0400 |
parents | a9ff2b0c11dd |
children | ec0a2601bc76 |
comparison
equal
deleted
inserted
replaced
39254:284440041141 | 39255:4019b4542e61 |
---|---|
18 import time | 18 import time |
19 import traceback | 19 import traceback |
20 | 20 |
21 | 21 |
22 from .i18n import _ | 22 from .i18n import _ |
23 | |
24 from hgdemandimport import tracing | |
23 | 25 |
24 from . import ( | 26 from . import ( |
25 cmdutil, | 27 cmdutil, |
26 color, | 28 color, |
27 commands, | 29 commands, |
82 raise exc | 84 raise exc |
83 | 85 |
84 def run(): | 86 def run(): |
85 "run the command in sys.argv" | 87 "run the command in sys.argv" |
86 initstdio() | 88 initstdio() |
87 req = request(pycompat.sysargv[1:]) | 89 with tracing.log('parse args into request'): |
90 req = request(pycompat.sysargv[1:]) | |
88 err = None | 91 err = None |
89 try: | 92 try: |
90 status = dispatch(req) | 93 status = dispatch(req) |
91 except error.StdioError as e: | 94 except error.StdioError as e: |
92 err = e | 95 err = e |
174 def _formatargs(args): | 177 def _formatargs(args): |
175 return ' '.join(procutil.shellquote(a) for a in args) | 178 return ' '.join(procutil.shellquote(a) for a in args) |
176 | 179 |
177 def dispatch(req): | 180 def dispatch(req): |
178 """run the command specified in req.args; returns an integer status code""" | 181 """run the command specified in req.args; returns an integer status code""" |
179 if req.ferr: | 182 with tracing.log('dispatch.dispatch'): |
180 ferr = req.ferr | |
181 elif req.ui: | |
182 ferr = req.ui.ferr | |
183 else: | |
184 ferr = procutil.stderr | |
185 | |
186 try: | |
187 if not req.ui: | |
188 req.ui = uimod.ui.load() | |
189 req.earlyoptions.update(_earlyparseopts(req.ui, req.args)) | |
190 if req.earlyoptions['traceback']: | |
191 req.ui.setconfig('ui', 'traceback', 'on', '--traceback') | |
192 | |
193 # set ui streams from the request | |
194 if req.fin: | |
195 req.ui.fin = req.fin | |
196 if req.fout: | |
197 req.ui.fout = req.fout | |
198 if req.ferr: | 183 if req.ferr: |
199 req.ui.ferr = req.ferr | 184 ferr = req.ferr |
200 except error.Abort as inst: | 185 elif req.ui: |
201 ferr.write(_("abort: %s\n") % inst) | 186 ferr = req.ui.ferr |
202 if inst.hint: | 187 else: |
203 ferr.write(_("(%s)\n") % inst.hint) | 188 ferr = procutil.stderr |
204 return -1 | 189 |
205 except error.ParseError as inst: | 190 try: |
206 _formatparse(ferr.write, inst) | 191 if not req.ui: |
207 return -1 | 192 req.ui = uimod.ui.load() |
208 | 193 req.earlyoptions.update(_earlyparseopts(req.ui, req.args)) |
209 msg = _formatargs(req.args) | 194 if req.earlyoptions['traceback']: |
210 starttime = util.timer() | 195 req.ui.setconfig('ui', 'traceback', 'on', '--traceback') |
211 ret = 1 # default of Python exit code on unhandled exception | 196 |
212 try: | 197 # set ui streams from the request |
213 ret = _runcatch(req) or 0 | 198 if req.fin: |
214 except error.ProgrammingError as inst: | 199 req.ui.fin = req.fin |
215 req.ui.error(_('** ProgrammingError: %s\n') % inst) | 200 if req.fout: |
216 if inst.hint: | 201 req.ui.fout = req.fout |
217 req.ui.error(_('** (%s)\n') % inst.hint) | 202 if req.ferr: |
218 raise | 203 req.ui.ferr = req.ferr |
219 except KeyboardInterrupt as inst: | 204 except error.Abort as inst: |
220 try: | 205 ferr.write(_("abort: %s\n") % inst) |
221 if isinstance(inst, error.SignalInterrupt): | 206 if inst.hint: |
222 msg = _("killed!\n") | 207 ferr.write(_("(%s)\n") % inst.hint) |
223 else: | 208 return -1 |
224 msg = _("interrupted!\n") | 209 except error.ParseError as inst: |
225 req.ui.error(msg) | 210 _formatparse(ferr.write, inst) |
226 except error.SignalInterrupt: | 211 return -1 |
227 # maybe pager would quit without consuming all the output, and | 212 |
228 # SIGPIPE was raised. we cannot print anything in this case. | 213 msg = _formatargs(req.args) |
229 pass | 214 starttime = util.timer() |
230 except IOError as inst: | 215 ret = 1 # default of Python exit code on unhandled exception |
231 if inst.errno != errno.EPIPE: | 216 try: |
217 ret = _runcatch(req) or 0 | |
218 except error.ProgrammingError as inst: | |
219 req.ui.error(_('** ProgrammingError: %s\n') % inst) | |
220 if inst.hint: | |
221 req.ui.error(_('** (%s)\n') % inst.hint) | |
222 raise | |
223 except KeyboardInterrupt as inst: | |
224 try: | |
225 if isinstance(inst, error.SignalInterrupt): | |
226 msg = _("killed!\n") | |
227 else: | |
228 msg = _("interrupted!\n") | |
229 req.ui.error(msg) | |
230 except error.SignalInterrupt: | |
231 # maybe pager would quit without consuming all the output, and | |
232 # SIGPIPE was raised. we cannot print anything in this case. | |
233 pass | |
234 except IOError as inst: | |
235 if inst.errno != errno.EPIPE: | |
236 raise | |
237 ret = -1 | |
238 finally: | |
239 duration = util.timer() - starttime | |
240 req.ui.flush() | |
241 if req.ui.logblockedtimes: | |
242 req.ui._blockedtimes['command_duration'] = duration * 1000 | |
243 req.ui.log('uiblocked', 'ui blocked ms', | |
244 **pycompat.strkwargs(req.ui._blockedtimes)) | |
245 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n", | |
246 msg, ret & 255, duration) | |
247 try: | |
248 req._runexithandlers() | |
249 except: # exiting, so no re-raises | |
250 ret = ret or -1 | |
251 return ret | |
252 | |
253 def _runcatch(req): | |
254 with tracing.log('dispatch._runcatch'): | |
255 def catchterm(*args): | |
256 raise error.SignalInterrupt | |
257 | |
258 ui = req.ui | |
259 try: | |
260 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM': | |
261 num = getattr(signal, name, None) | |
262 if num: | |
263 signal.signal(num, catchterm) | |
264 except ValueError: | |
265 pass # happens if called in a thread | |
266 | |
267 def _runcatchfunc(): | |
268 realcmd = None | |
269 try: | |
270 cmdargs = fancyopts.fancyopts( | |
271 req.args[:], commands.globalopts, {}) | |
272 cmd = cmdargs[0] | |
273 aliases, entry = cmdutil.findcmd(cmd, commands.table, False) | |
274 realcmd = aliases[0] | |
275 except (error.UnknownCommand, error.AmbiguousCommand, | |
276 IndexError, getopt.GetoptError): | |
277 # Don't handle this here. We know the command is | |
278 # invalid, but all we're worried about for now is that | |
279 # it's not a command that server operators expect to | |
280 # be safe to offer to users in a sandbox. | |
281 pass | |
282 if realcmd == 'serve' and '--stdio' in cmdargs: | |
283 # We want to constrain 'hg serve --stdio' instances pretty | |
284 # closely, as many shared-ssh access tools want to grant | |
285 # access to run *only* 'hg -R $repo serve --stdio'. We | |
286 # restrict to exactly that set of arguments, and prohibit | |
287 # any repo name that starts with '--' to prevent | |
288 # shenanigans wherein a user does something like pass | |
289 # --debugger or --config=ui.debugger=1 as a repo | |
290 # name. This used to actually run the debugger. | |
291 if (len(req.args) != 4 or | |
292 req.args[0] != '-R' or | |
293 req.args[1].startswith('--') or | |
294 req.args[2] != 'serve' or | |
295 req.args[3] != '--stdio'): | |
296 raise error.Abort( | |
297 _('potentially unsafe serve --stdio invocation: %s') % | |
298 (stringutil.pprint(req.args),)) | |
299 | |
300 try: | |
301 debugger = 'pdb' | |
302 debugtrace = { | |
303 'pdb': pdb.set_trace | |
304 } | |
305 debugmortem = { | |
306 'pdb': pdb.post_mortem | |
307 } | |
308 | |
309 # read --config before doing anything else | |
310 # (e.g. to change trust settings for reading .hg/hgrc) | |
311 cfgs = _parseconfig(req.ui, req.earlyoptions['config']) | |
312 | |
313 if req.repo: | |
314 # copy configs that were passed on the cmdline (--config) to | |
315 # the repo ui | |
316 for sec, name, val in cfgs: | |
317 req.repo.ui.setconfig(sec, name, val, source='--config') | |
318 | |
319 # developer config: ui.debugger | |
320 debugger = ui.config("ui", "debugger") | |
321 debugmod = pdb | |
322 if not debugger or ui.plain(): | |
323 # if we are in HGPLAIN mode, then disable custom debugging | |
324 debugger = 'pdb' | |
325 elif req.earlyoptions['debugger']: | |
326 # This import can be slow for fancy debuggers, so only | |
327 # do it when absolutely necessary, i.e. when actual | |
328 # debugging has been requested | |
329 with demandimport.deactivated(): | |
330 try: | |
331 debugmod = __import__(debugger) | |
332 except ImportError: | |
333 pass # Leave debugmod = pdb | |
334 | |
335 debugtrace[debugger] = debugmod.set_trace | |
336 debugmortem[debugger] = debugmod.post_mortem | |
337 | |
338 # enter the debugger before command execution | |
339 if req.earlyoptions['debugger']: | |
340 ui.warn(_("entering debugger - " | |
341 "type c to continue starting hg or h for help\n")) | |
342 | |
343 if (debugger != 'pdb' and | |
344 debugtrace[debugger] == debugtrace['pdb']): | |
345 ui.warn(_("%s debugger specified " | |
346 "but its module was not found\n") % debugger) | |
347 with demandimport.deactivated(): | |
348 debugtrace[debugger]() | |
349 try: | |
350 return _dispatch(req) | |
351 finally: | |
352 ui.flush() | |
353 except: # re-raises | |
354 # enter the debugger when we hit an exception | |
355 if req.earlyoptions['debugger']: | |
356 traceback.print_exc() | |
357 debugmortem[debugger](sys.exc_info()[2]) | |
232 raise | 358 raise |
233 ret = -1 | 359 return _callcatch(ui, _runcatchfunc) |
234 finally: | |
235 duration = util.timer() - starttime | |
236 req.ui.flush() | |
237 if req.ui.logblockedtimes: | |
238 req.ui._blockedtimes['command_duration'] = duration * 1000 | |
239 req.ui.log('uiblocked', 'ui blocked ms', | |
240 **pycompat.strkwargs(req.ui._blockedtimes)) | |
241 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n", | |
242 msg, ret & 255, duration) | |
243 try: | |
244 req._runexithandlers() | |
245 except: # exiting, so no re-raises | |
246 ret = ret or -1 | |
247 return ret | |
248 | |
249 def _runcatch(req): | |
250 def catchterm(*args): | |
251 raise error.SignalInterrupt | |
252 | |
253 ui = req.ui | |
254 try: | |
255 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM': | |
256 num = getattr(signal, name, None) | |
257 if num: | |
258 signal.signal(num, catchterm) | |
259 except ValueError: | |
260 pass # happens if called in a thread | |
261 | |
262 def _runcatchfunc(): | |
263 realcmd = None | |
264 try: | |
265 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {}) | |
266 cmd = cmdargs[0] | |
267 aliases, entry = cmdutil.findcmd(cmd, commands.table, False) | |
268 realcmd = aliases[0] | |
269 except (error.UnknownCommand, error.AmbiguousCommand, | |
270 IndexError, getopt.GetoptError): | |
271 # Don't handle this here. We know the command is | |
272 # invalid, but all we're worried about for now is that | |
273 # it's not a command that server operators expect to | |
274 # be safe to offer to users in a sandbox. | |
275 pass | |
276 if realcmd == 'serve' and '--stdio' in cmdargs: | |
277 # We want to constrain 'hg serve --stdio' instances pretty | |
278 # closely, as many shared-ssh access tools want to grant | |
279 # access to run *only* 'hg -R $repo serve --stdio'. We | |
280 # restrict to exactly that set of arguments, and prohibit | |
281 # any repo name that starts with '--' to prevent | |
282 # shenanigans wherein a user does something like pass | |
283 # --debugger or --config=ui.debugger=1 as a repo | |
284 # name. This used to actually run the debugger. | |
285 if (len(req.args) != 4 or | |
286 req.args[0] != '-R' or | |
287 req.args[1].startswith('--') or | |
288 req.args[2] != 'serve' or | |
289 req.args[3] != '--stdio'): | |
290 raise error.Abort( | |
291 _('potentially unsafe serve --stdio invocation: %s') % | |
292 (stringutil.pprint(req.args),)) | |
293 | |
294 try: | |
295 debugger = 'pdb' | |
296 debugtrace = { | |
297 'pdb': pdb.set_trace | |
298 } | |
299 debugmortem = { | |
300 'pdb': pdb.post_mortem | |
301 } | |
302 | |
303 # read --config before doing anything else | |
304 # (e.g. to change trust settings for reading .hg/hgrc) | |
305 cfgs = _parseconfig(req.ui, req.earlyoptions['config']) | |
306 | |
307 if req.repo: | |
308 # copy configs that were passed on the cmdline (--config) to | |
309 # the repo ui | |
310 for sec, name, val in cfgs: | |
311 req.repo.ui.setconfig(sec, name, val, source='--config') | |
312 | |
313 # developer config: ui.debugger | |
314 debugger = ui.config("ui", "debugger") | |
315 debugmod = pdb | |
316 if not debugger or ui.plain(): | |
317 # if we are in HGPLAIN mode, then disable custom debugging | |
318 debugger = 'pdb' | |
319 elif req.earlyoptions['debugger']: | |
320 # This import can be slow for fancy debuggers, so only | |
321 # do it when absolutely necessary, i.e. when actual | |
322 # debugging has been requested | |
323 with demandimport.deactivated(): | |
324 try: | |
325 debugmod = __import__(debugger) | |
326 except ImportError: | |
327 pass # Leave debugmod = pdb | |
328 | |
329 debugtrace[debugger] = debugmod.set_trace | |
330 debugmortem[debugger] = debugmod.post_mortem | |
331 | |
332 # enter the debugger before command execution | |
333 if req.earlyoptions['debugger']: | |
334 ui.warn(_("entering debugger - " | |
335 "type c to continue starting hg or h for help\n")) | |
336 | |
337 if (debugger != 'pdb' and | |
338 debugtrace[debugger] == debugtrace['pdb']): | |
339 ui.warn(_("%s debugger specified " | |
340 "but its module was not found\n") % debugger) | |
341 with demandimport.deactivated(): | |
342 debugtrace[debugger]() | |
343 try: | |
344 return _dispatch(req) | |
345 finally: | |
346 ui.flush() | |
347 except: # re-raises | |
348 # enter the debugger when we hit an exception | |
349 if req.earlyoptions['debugger']: | |
350 traceback.print_exc() | |
351 debugmortem[debugger](sys.exc_info()[2]) | |
352 raise | |
353 | |
354 return _callcatch(ui, _runcatchfunc) | |
355 | 360 |
356 def _callcatch(ui, func): | 361 def _callcatch(ui, func): |
357 """like scmutil.callcatch but handles more high-level exceptions about | 362 """like scmutil.callcatch but handles more high-level exceptions about |
358 config parsing and commands. besides, use handlecommandexception to handle | 363 config parsing and commands. besides, use handlecommandexception to handle |
359 uncaught exceptions. | 364 uncaught exceptions. |