Details
Details
- Reviewers
 - None
 - Group Reviewers
 hg-reviewers - Commits
 - rHG4019b4542e61: dispatch: have dispatch.dispatch and dispatch._runcatch emit trace events
 
Diff Detail
Diff Detail
- Repository
 - rHG Mercurial
 - Lint
 Lint Skipped - Unit
 Unit Tests Skipped 
( )
| hg-reviewers | 
| Lint Skipped | 
| Unit Tests Skipped | 
| import signal | import signal | ||||
| import sys | import sys | ||||
| import time | import time | ||||
| import traceback | import traceback | ||||
| from .i18n import _ | from .i18n import _ | ||||
| from hgdemandimport import tracing | |||||
| from . import ( | from . import ( | ||||
| cmdutil, | cmdutil, | ||||
| color, | color, | ||||
| commands, | commands, | ||||
| demandimport, | demandimport, | ||||
| encoding, | encoding, | ||||
| error, | error, | ||||
| extensions, | extensions, | ||||
| self.ui.traceback(force=True) | self.ui.traceback(force=True) | ||||
| finally: | finally: | ||||
| if exc is not None: | if exc is not None: | ||||
| raise exc | raise exc | ||||
| def run(): | def run(): | ||||
| "run the command in sys.argv" | "run the command in sys.argv" | ||||
| initstdio() | initstdio() | ||||
| with tracing.log('parse args into request'): | |||||
| req = request(pycompat.sysargv[1:]) | req = request(pycompat.sysargv[1:]) | ||||
| err = None | err = None | ||||
| try: | try: | ||||
| status = dispatch(req) | status = dispatch(req) | ||||
| except error.StdioError as e: | except error.StdioError as e: | ||||
| err = e | err = e | ||||
| status = -1 | status = -1 | ||||
| # In all cases we try to flush stdio streams. | # In all cases we try to flush stdio streams. | ||||
| if inst.hint: | if inst.hint: | ||||
| write(_("(%s)\n") % inst.hint) | write(_("(%s)\n") % inst.hint) | ||||
| def _formatargs(args): | def _formatargs(args): | ||||
| return ' '.join(procutil.shellquote(a) for a in args) | return ' '.join(procutil.shellquote(a) for a in args) | ||||
| def dispatch(req): | def dispatch(req): | ||||
| """run the command specified in req.args; returns an integer status code""" | """run the command specified in req.args; returns an integer status code""" | ||||
| with tracing.log('dispatch.dispatch'): | |||||
| if req.ferr: | if req.ferr: | ||||
| ferr = req.ferr | ferr = req.ferr | ||||
| elif req.ui: | elif req.ui: | ||||
| ferr = req.ui.ferr | ferr = req.ui.ferr | ||||
| else: | else: | ||||
| ferr = procutil.stderr | ferr = procutil.stderr | ||||
| try: | try: | ||||
| if not req.ui: | if not req.ui: | ||||
| req.ui = uimod.ui.load() | req.ui = uimod.ui.load() | ||||
| req.earlyoptions.update(_earlyparseopts(req.ui, req.args)) | req.earlyoptions.update(_earlyparseopts(req.ui, req.args)) | ||||
| if req.earlyoptions['traceback']: | if req.earlyoptions['traceback']: | ||||
| req.ui.setconfig('ui', 'traceback', 'on', '--traceback') | req.ui.setconfig('ui', 'traceback', 'on', '--traceback') | ||||
| # set ui streams from the request | # set ui streams from the request | ||||
| if req.fin: | if req.fin: | ||||
| req.ui.fin = req.fin | req.ui.fin = req.fin | ||||
| if req.fout: | if req.fout: | ||||
| req.ui.fout = req.fout | req.ui.fout = req.fout | ||||
| if req.ferr: | if req.ferr: | ||||
| req.ui.ferr = req.ferr | req.ui.ferr = req.ferr | ||||
| except error.Abort as inst: | except error.Abort as inst: | ||||
| ferr.write(_("abort: %s\n") % inst) | ferr.write(_("abort: %s\n") % inst) | ||||
| if inst.hint: | if inst.hint: | ||||
| ferr.write(_("(%s)\n") % inst.hint) | ferr.write(_("(%s)\n") % inst.hint) | ||||
| return -1 | return -1 | ||||
| except error.ParseError as inst: | except error.ParseError as inst: | ||||
| _formatparse(ferr.write, inst) | _formatparse(ferr.write, inst) | ||||
| return -1 | return -1 | ||||
| msg = _formatargs(req.args) | msg = _formatargs(req.args) | ||||
| starttime = util.timer() | starttime = util.timer() | ||||
| ret = 1 # default of Python exit code on unhandled exception | ret = 1 # default of Python exit code on unhandled exception | ||||
| try: | try: | ||||
| ret = _runcatch(req) or 0 | ret = _runcatch(req) or 0 | ||||
| except error.ProgrammingError as inst: | except error.ProgrammingError as inst: | ||||
| req.ui.error(_('** ProgrammingError: %s\n') % inst) | req.ui.error(_('** ProgrammingError: %s\n') % inst) | ||||
| if inst.hint: | if inst.hint: | ||||
| req.ui.error(_('** (%s)\n') % inst.hint) | req.ui.error(_('** (%s)\n') % inst.hint) | ||||
| raise | raise | ||||
| except KeyboardInterrupt as inst: | except KeyboardInterrupt as inst: | ||||
| try: | try: | ||||
| if isinstance(inst, error.SignalInterrupt): | if isinstance(inst, error.SignalInterrupt): | ||||
| msg = _("killed!\n") | msg = _("killed!\n") | ||||
| else: | else: | ||||
| msg = _("interrupted!\n") | msg = _("interrupted!\n") | ||||
| req.ui.error(msg) | req.ui.error(msg) | ||||
| except error.SignalInterrupt: | except error.SignalInterrupt: | ||||
| # maybe pager would quit without consuming all the output, and | # maybe pager would quit without consuming all the output, and | ||||
| # SIGPIPE was raised. we cannot print anything in this case. | # SIGPIPE was raised. we cannot print anything in this case. | ||||
| pass | pass | ||||
| except IOError as inst: | except IOError as inst: | ||||
| if inst.errno != errno.EPIPE: | if inst.errno != errno.EPIPE: | ||||
| raise | raise | ||||
| ret = -1 | ret = -1 | ||||
| finally: | finally: | ||||
| duration = util.timer() - starttime | duration = util.timer() - starttime | ||||
| req.ui.flush() | req.ui.flush() | ||||
| if req.ui.logblockedtimes: | if req.ui.logblockedtimes: | ||||
| req.ui._blockedtimes['command_duration'] = duration * 1000 | req.ui._blockedtimes['command_duration'] = duration * 1000 | ||||
| req.ui.log('uiblocked', 'ui blocked ms', | req.ui.log('uiblocked', 'ui blocked ms', | ||||
| **pycompat.strkwargs(req.ui._blockedtimes)) | **pycompat.strkwargs(req.ui._blockedtimes)) | ||||
| req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n", | req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n", | ||||
| msg, ret & 255, duration) | msg, ret & 255, duration) | ||||
| try: | try: | ||||
| req._runexithandlers() | req._runexithandlers() | ||||
| except: # exiting, so no re-raises | except: # exiting, so no re-raises | ||||
| ret = ret or -1 | ret = ret or -1 | ||||
| return ret | return ret | ||||
| def _runcatch(req): | def _runcatch(req): | ||||
| with tracing.log('dispatch._runcatch'): | |||||
| def catchterm(*args): | def catchterm(*args): | ||||
| raise error.SignalInterrupt | raise error.SignalInterrupt | ||||
| ui = req.ui | ui = req.ui | ||||
| try: | try: | ||||
| for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM': | for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM': | ||||
| num = getattr(signal, name, None) | num = getattr(signal, name, None) | ||||
| if num: | if num: | ||||
| signal.signal(num, catchterm) | signal.signal(num, catchterm) | ||||
| except ValueError: | except ValueError: | ||||
| pass # happens if called in a thread | pass # happens if called in a thread | ||||
| def _runcatchfunc(): | def _runcatchfunc(): | ||||
| realcmd = None | realcmd = None | ||||
| try: | try: | ||||
| cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {}) | cmdargs = fancyopts.fancyopts( | ||||
| req.args[:], commands.globalopts, {}) | |||||
| cmd = cmdargs[0] | cmd = cmdargs[0] | ||||
| aliases, entry = cmdutil.findcmd(cmd, commands.table, False) | aliases, entry = cmdutil.findcmd(cmd, commands.table, False) | ||||
| realcmd = aliases[0] | realcmd = aliases[0] | ||||
| except (error.UnknownCommand, error.AmbiguousCommand, | except (error.UnknownCommand, error.AmbiguousCommand, | ||||
| IndexError, getopt.GetoptError): | IndexError, getopt.GetoptError): | ||||
| # Don't handle this here. We know the command is | # Don't handle this here. We know the command is | ||||
| # invalid, but all we're worried about for now is that | # invalid, but all we're worried about for now is that | ||||
| # it's not a command that server operators expect to | # it's not a command that server operators expect to | ||||
| # be safe to offer to users in a sandbox. | # be safe to offer to users in a sandbox. | ||||
| pass | pass | ||||
| if realcmd == 'serve' and '--stdio' in cmdargs: | if realcmd == 'serve' and '--stdio' in cmdargs: | ||||
| # We want to constrain 'hg serve --stdio' instances pretty | # We want to constrain 'hg serve --stdio' instances pretty | ||||
| # closely, as many shared-ssh access tools want to grant | # closely, as many shared-ssh access tools want to grant | ||||
| # access to run *only* 'hg -R $repo serve --stdio'. We | # access to run *only* 'hg -R $repo serve --stdio'. We | ||||
| # restrict to exactly that set of arguments, and prohibit | # restrict to exactly that set of arguments, and prohibit | ||||
| # any repo name that starts with '--' to prevent | # any repo name that starts with '--' to prevent | ||||
| # shenanigans wherein a user does something like pass | # shenanigans wherein a user does something like pass | ||||
| # --debugger or --config=ui.debugger=1 as a repo | # --debugger or --config=ui.debugger=1 as a repo | ||||
| # name. This used to actually run the debugger. | # name. This used to actually run the debugger. | ||||
| if (len(req.args) != 4 or | if (len(req.args) != 4 or | ||||
| req.args[0] != '-R' or | req.args[0] != '-R' or | ||||
| req.args[1].startswith('--') or | req.args[1].startswith('--') or | ||||
| req.args[2] != 'serve' or | req.args[2] != 'serve' or | ||||
| req.args[3] != '--stdio'): | req.args[3] != '--stdio'): | ||||
| raise error.Abort( | raise error.Abort( | ||||
| _('potentially unsafe serve --stdio invocation: %s') % | _('potentially unsafe serve --stdio invocation: %s') % | ||||
| (stringutil.pprint(req.args),)) | (stringutil.pprint(req.args),)) | ||||
| try: | try: | ||||
| debugger = 'pdb' | debugger = 'pdb' | ||||
| debugtrace = { | debugtrace = { | ||||
| 'pdb': pdb.set_trace | 'pdb': pdb.set_trace | ||||
| } | } | ||||
| debugmortem = { | debugmortem = { | ||||
| 'pdb': pdb.post_mortem | 'pdb': pdb.post_mortem | ||||
| } | } | ||||
| # read --config before doing anything else | # read --config before doing anything else | ||||
| # (e.g. to change trust settings for reading .hg/hgrc) | # (e.g. to change trust settings for reading .hg/hgrc) | ||||
| cfgs = _parseconfig(req.ui, req.earlyoptions['config']) | cfgs = _parseconfig(req.ui, req.earlyoptions['config']) | ||||
| if req.repo: | if req.repo: | ||||
| # copy configs that were passed on the cmdline (--config) to | # copy configs that were passed on the cmdline (--config) to | ||||
| # the repo ui | # the repo ui | ||||
| for sec, name, val in cfgs: | for sec, name, val in cfgs: | ||||
| req.repo.ui.setconfig(sec, name, val, source='--config') | req.repo.ui.setconfig(sec, name, val, source='--config') | ||||
| # developer config: ui.debugger | # developer config: ui.debugger | ||||
| debugger = ui.config("ui", "debugger") | debugger = ui.config("ui", "debugger") | ||||
| debugmod = pdb | debugmod = pdb | ||||
| if not debugger or ui.plain(): | if not debugger or ui.plain(): | ||||
| # if we are in HGPLAIN mode, then disable custom debugging | # if we are in HGPLAIN mode, then disable custom debugging | ||||
| debugger = 'pdb' | debugger = 'pdb' | ||||
| elif req.earlyoptions['debugger']: | elif req.earlyoptions['debugger']: | ||||
| # This import can be slow for fancy debuggers, so only | # This import can be slow for fancy debuggers, so only | ||||
| # do it when absolutely necessary, i.e. when actual | # do it when absolutely necessary, i.e. when actual | ||||
| # debugging has been requested | # debugging has been requested | ||||
| with demandimport.deactivated(): | with demandimport.deactivated(): | ||||
| try: | try: | ||||
| debugmod = __import__(debugger) | debugmod = __import__(debugger) | ||||
| except ImportError: | except ImportError: | ||||
| pass # Leave debugmod = pdb | pass # Leave debugmod = pdb | ||||
| debugtrace[debugger] = debugmod.set_trace | debugtrace[debugger] = debugmod.set_trace | ||||
| debugmortem[debugger] = debugmod.post_mortem | debugmortem[debugger] = debugmod.post_mortem | ||||
| # enter the debugger before command execution | # enter the debugger before command execution | ||||
| if req.earlyoptions['debugger']: | if req.earlyoptions['debugger']: | ||||
| ui.warn(_("entering debugger - " | ui.warn(_("entering debugger - " | ||||
| "type c to continue starting hg or h for help\n")) | "type c to continue starting hg or h for help\n")) | ||||
| if (debugger != 'pdb' and | if (debugger != 'pdb' and | ||||
| debugtrace[debugger] == debugtrace['pdb']): | debugtrace[debugger] == debugtrace['pdb']): | ||||
| ui.warn(_("%s debugger specified " | ui.warn(_("%s debugger specified " | ||||
| "but its module was not found\n") % debugger) | "but its module was not found\n") % debugger) | ||||
| with demandimport.deactivated(): | with demandimport.deactivated(): | ||||
| debugtrace[debugger]() | debugtrace[debugger]() | ||||
| try: | try: | ||||
| return _dispatch(req) | return _dispatch(req) | ||||
| finally: | finally: | ||||
| ui.flush() | ui.flush() | ||||
| except: # re-raises | except: # re-raises | ||||
| # enter the debugger when we hit an exception | # enter the debugger when we hit an exception | ||||
| if req.earlyoptions['debugger']: | if req.earlyoptions['debugger']: | ||||
| traceback.print_exc() | traceback.print_exc() | ||||
| debugmortem[debugger](sys.exc_info()[2]) | debugmortem[debugger](sys.exc_info()[2]) | ||||
| raise | raise | ||||
| return _callcatch(ui, _runcatchfunc) | return _callcatch(ui, _runcatchfunc) | ||||
| def _callcatch(ui, func): | def _callcatch(ui, func): | ||||
| """like scmutil.callcatch but handles more high-level exceptions about | """like scmutil.callcatch but handles more high-level exceptions about | ||||
| config parsing and commands. besides, use handlecommandexception to handle | config parsing and commands. besides, use handlecommandexception to handle | ||||
| uncaught exceptions. | uncaught exceptions. | ||||
| """ | """ | ||||
| try: | try: | ||||
| return scmutil.callcatch(ui, func) | return scmutil.callcatch(ui, func) | ||||