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) |