comparison mercurial/dispatch.py @ 31956:c13ff31818b0

ui: add special-purpose atexit functionality In spite of its longstanding use, Python's built-in atexit code is not suitable for Mercurial's purposes, for several reasons: * Handlers run after application code has finished. * Because of this, the code that runs handlers swallows exceptions (since there's no possible stacktrace to associate errors with). If we're lucky, we'll get something spat out to stderr (if stderr still works), which of course isn't any use in a big deployment where it's important that exceptions get logged and aggregated. * Mercurial's current atexit handlers make unfortunate assumptions about process state (specifically stdio) that, coupled with the above problems, make it impossible to deal with certain categories of error (try "hg status > /dev/full" on a Linux box). * In Python 3, the atexit implementation is completely hidden, so we can't hijack the platform's atexit code to run handlers at a time of our choosing. As a result, here's a perfectly cromulent atexit-like implementation over which we have control. This lets us decide exactly when the handlers run (after each request has completed), and control what the process state is when that occurs (and afterwards).
author Bryan O'Sullivan <bryano@fb.com>
date Tue, 11 Apr 2017 14:54:12 -0700
parents 2632df096fc0
children de5c9d0e02ea
comparison
equal deleted inserted replaced
31955:4c2c30bc38b4 31956:c13ff31818b0
57 # input/output/error streams 57 # input/output/error streams
58 self.fin = fin 58 self.fin = fin
59 self.fout = fout 59 self.fout = fout
60 self.ferr = ferr 60 self.ferr = ferr
61 61
62 def _runexithandlers(self):
63 exc = None
64 handlers = self.ui._exithandlers
65 try:
66 while handlers:
67 func, args, kwargs = handlers.pop()
68 try:
69 func(*args, **kwargs)
70 except: # re-raises below
71 if exc is None:
72 exc = sys.exc_info()[1]
73 self.ui.warn(('error in exit handlers:\n'))
74 self.ui.traceback(force=True)
75 finally:
76 if exc is not None:
77 raise exc
78
62 def run(): 79 def run():
63 "run the command in sys.argv" 80 "run the command in sys.argv"
64 sys.exit((dispatch(request(pycompat.sysargv[1:])) or 0) & 255) 81 sys.exit((dispatch(request(pycompat.sysargv[1:])) or 0) & 255)
65 82
66 def _getsimilar(symbols, value): 83 def _getsimilar(symbols, value):
144 if req.ui.logblockedtimes: 161 if req.ui.logblockedtimes:
145 req.ui._blockedtimes['command_duration'] = duration * 1000 162 req.ui._blockedtimes['command_duration'] = duration * 1000
146 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes) 163 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
147 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n", 164 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
148 msg, ret or 0, duration) 165 msg, ret or 0, duration)
166 try:
167 req._runexithandlers()
168 except: # exiting, so no re-raises
169 ret = ret or -1
149 return ret 170 return ret
150 171
151 def _runcatch(req): 172 def _runcatch(req):
152 def catchterm(*args): 173 def catchterm(*args):
153 raise error.SignalInterrupt 174 raise error.SignalInterrupt