Show More
@@ -0,0 +1,83 b'' | |||
|
1 | Dummy extension simulating unsafe long running command | |
|
2 | $ cat > sleepext.py <<EOF | |
|
3 | > import time | |
|
4 | > import itertools | |
|
5 | > | |
|
6 | > from mercurial import registrar | |
|
7 | > from mercurial.i18n import _ | |
|
8 | > | |
|
9 | > cmdtable = {} | |
|
10 | > command = registrar.command(cmdtable) | |
|
11 | > | |
|
12 | > @command(b'sleep', [], _(b'TIME'), norepo=True) | |
|
13 | > def sleep(ui, sleeptime=b"1", **opts): | |
|
14 | > with ui.uninterruptable(): | |
|
15 | > for _i in itertools.repeat(None, int(sleeptime)): | |
|
16 | > time.sleep(1) | |
|
17 | > ui.warn(b"end of unsafe operation\n") | |
|
18 | > ui.warn(b"%s second(s) passed\n" % sleeptime) | |
|
19 | > EOF | |
|
20 | ||
|
21 | Kludge to emulate timeout(1) which is not generally available. | |
|
22 | $ cat > timeout.py <<EOF | |
|
23 | > from __future__ import print_function | |
|
24 | > import argparse | |
|
25 | > import signal | |
|
26 | > import subprocess | |
|
27 | > import sys | |
|
28 | > import time | |
|
29 | > | |
|
30 | > ap = argparse.ArgumentParser() | |
|
31 | > ap.add_argument('-s', nargs=1, default='SIGTERM') | |
|
32 | > ap.add_argument('duration', nargs=1, type=int) | |
|
33 | > ap.add_argument('argv', nargs='*') | |
|
34 | > opts = ap.parse_args() | |
|
35 | > try: | |
|
36 | > sig = int(opts.s[0]) | |
|
37 | > except ValueError: | |
|
38 | > sname = opts.s[0] | |
|
39 | > if not sname.startswith('SIG'): | |
|
40 | > sname = 'SIG' + sname | |
|
41 | > sig = getattr(signal, sname) | |
|
42 | > proc = subprocess.Popen(opts.argv) | |
|
43 | > time.sleep(opts.duration[0]) | |
|
44 | > proc.poll() | |
|
45 | > if proc.returncode is None: | |
|
46 | > proc.send_signal(sig) | |
|
47 | > proc.wait() | |
|
48 | > sys.exit(124) | |
|
49 | > EOF | |
|
50 | ||
|
51 | Set up repository | |
|
52 | $ hg init repo | |
|
53 | $ cd repo | |
|
54 | $ cat >> $HGRCPATH << EOF | |
|
55 | > [extensions] | |
|
56 | > sleepext = ../sleepext.py | |
|
57 | > EOF | |
|
58 | ||
|
59 | Test ctrl-c | |
|
60 | $ python $TESTTMP/timeout.py -s INT 1 hg sleep 2 | |
|
61 | interrupted! | |
|
62 | [124] | |
|
63 | ||
|
64 | $ cat >> $HGRCPATH << EOF | |
|
65 | > [experimental] | |
|
66 | > nointerrupt = yes | |
|
67 | > EOF | |
|
68 | ||
|
69 | $ python $TESTTMP/timeout.py -s INT 1 hg sleep 2 | |
|
70 | interrupted! | |
|
71 | [124] | |
|
72 | ||
|
73 | $ cat >> $HGRCPATH << EOF | |
|
74 | > [experimental] | |
|
75 | > nointerrupt-interactiveonly = False | |
|
76 | > EOF | |
|
77 | ||
|
78 | $ python $TESTTMP/timeout.py -s INT 1 hg sleep 2 | |
|
79 | shutting down cleanly | |
|
80 | press ^C again to terminate immediately (dangerous) | |
|
81 | end of unsafe operation | |
|
82 | interrupted! | |
|
83 | [124] |
@@ -560,6 +560,9 b" coreconfigitem('experimental', 'httppost" | |||
|
560 | 560 | coreconfigitem('experimental', 'mergedriver', |
|
561 | 561 | default=None, |
|
562 | 562 | ) |
|
563 | coreconfigitem('experimental', 'nointerrupt', default=False) | |
|
564 | coreconfigitem('experimental', 'nointerrupt-interactiveonly', default=True) | |
|
565 | ||
|
563 | 566 | coreconfigitem('experimental', 'obsmarkers-exchange-debug', |
|
564 | 567 | default=False, |
|
565 | 568 | ) |
@@ -224,6 +224,7 b' class ui(object):' | |||
|
224 | 224 | self._colormode = None |
|
225 | 225 | self._terminfoparams = {} |
|
226 | 226 | self._styles = {} |
|
227 | self._uninterruptible = False | |
|
227 | 228 | |
|
228 | 229 | if src: |
|
229 | 230 | self.fout = src.fout |
@@ -334,6 +335,37 b' class ui(object):' | |||
|
334 | 335 | self._blockedtimes[key + '_blocked'] += \ |
|
335 | 336 | (util.timer() - starttime) * 1000 |
|
336 | 337 | |
|
338 | @contextlib.contextmanager | |
|
339 | def uninterruptable(self): | |
|
340 | """Mark an operation as unsafe. | |
|
341 | ||
|
342 | Most operations on a repository are safe to interrupt, but a | |
|
343 | few are risky (for example repair.strip). This context manager | |
|
344 | lets you advise Mercurial that something risky is happening so | |
|
345 | that control-C etc can be blocked if desired. | |
|
346 | """ | |
|
347 | enabled = self.configbool('experimental', 'nointerrupt') | |
|
348 | if (enabled and | |
|
349 | self.configbool('experimental', 'nointerrupt-interactiveonly')): | |
|
350 | enabled = self.interactive() | |
|
351 | if self._uninterruptible or not enabled: | |
|
352 | # if nointerrupt support is turned off, the process isn't | |
|
353 | # interactive, or we're already in an uninterruptable | |
|
354 | # block, do nothing. | |
|
355 | yield | |
|
356 | return | |
|
357 | def warn(): | |
|
358 | self.warn(_("shutting down cleanly\n")) | |
|
359 | self.warn( | |
|
360 | _("press ^C again to terminate immediately (dangerous)\n")) | |
|
361 | return True | |
|
362 | with procutil.uninterruptable(warn): | |
|
363 | try: | |
|
364 | self._uninterruptible = True | |
|
365 | yield | |
|
366 | finally: | |
|
367 | self._uninterruptible = False | |
|
368 | ||
|
337 | 369 | def formatter(self, topic, opts): |
|
338 | 370 | return formatter.formatter(self, self, topic, opts) |
|
339 | 371 |
@@ -415,3 +415,36 b' def rundetached(args, condfn):' | |||
|
415 | 415 | finally: |
|
416 | 416 | if prevhandler is not None: |
|
417 | 417 | signal.signal(signal.SIGCHLD, prevhandler) |
|
418 | ||
|
419 | @contextlib.contextmanager | |
|
420 | def uninterruptable(warn): | |
|
421 | """Inhibit SIGINT handling on a region of code. | |
|
422 | ||
|
423 | Note that if this is called in a non-main thread, it turns into a no-op. | |
|
424 | ||
|
425 | Args: | |
|
426 | warn: A callable which takes no arguments, and returns True if the | |
|
427 | previous signal handling should be restored. | |
|
428 | """ | |
|
429 | ||
|
430 | oldsiginthandler = [signal.getsignal(signal.SIGINT)] | |
|
431 | shouldbail = [] | |
|
432 | ||
|
433 | def disabledsiginthandler(*args): | |
|
434 | if warn(): | |
|
435 | signal.signal(signal.SIGINT, oldsiginthandler[0]) | |
|
436 | del oldsiginthandler[0] | |
|
437 | shouldbail.append(True) | |
|
438 | ||
|
439 | try: | |
|
440 | try: | |
|
441 | signal.signal(signal.SIGINT, disabledsiginthandler) | |
|
442 | except ValueError: | |
|
443 | # wrong thread, oh well, we tried | |
|
444 | del oldsiginthandler[0] | |
|
445 | yield | |
|
446 | finally: | |
|
447 | if oldsiginthandler: | |
|
448 | signal.signal(signal.SIGINT, oldsiginthandler[0]) | |
|
449 | if shouldbail: | |
|
450 | raise KeyboardInterrupt |
General Comments 0
You need to be logged in to leave comments.
Login now