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.