Show More
@@ -0,0 +1,176 b'' | |||||
|
1 | #!/usr/bin/env python3 | |||
|
2 | from __future__ import print_function | |||
|
3 | ||||
|
4 | import os | |||
|
5 | import subprocess | |||
|
6 | import sys | |||
|
7 | import threading | |||
|
8 | import time | |||
|
9 | ||||
|
10 | # we cannot use mercurial.testing as long as python2 is not dropped as the test will only install the mercurial module for python2 in python2 run | |||
|
11 | ||||
|
12 | ||||
|
13 | def _timeout_factor(): | |||
|
14 | """return the current modification to timeout""" | |||
|
15 | default = int(os.environ.get('HGTEST_TIMEOUT_DEFAULT', 360)) | |||
|
16 | current = int(os.environ.get('HGTEST_TIMEOUT', default)) | |||
|
17 | if current == 0: | |||
|
18 | return 1 | |||
|
19 | return current / float(default) | |||
|
20 | ||||
|
21 | ||||
|
22 | def wait_file(path, timeout=10): | |||
|
23 | timeout *= _timeout_factor() | |||
|
24 | start = time.time() | |||
|
25 | while not os.path.exists(path): | |||
|
26 | if (time.time() - start) > timeout: | |||
|
27 | raise RuntimeError(b"timed out waiting for file: %s" % path) | |||
|
28 | time.sleep(0.01) | |||
|
29 | ||||
|
30 | ||||
|
31 | def write_file(path, content=b''): | |||
|
32 | with open(path, 'wb') as f: | |||
|
33 | f.write(content) | |||
|
34 | ||||
|
35 | ||||
|
36 | # end of mercurial.testing content | |||
|
37 | ||||
|
38 | if sys.version_info[0] < 3: | |||
|
39 | print('SIGPIPE-HELPER: script should run with Python 3', file=sys.stderr) | |||
|
40 | sys.exit(255) | |||
|
41 | ||||
|
42 | ||||
|
43 | def sysbytes(s): | |||
|
44 | return s.encode('utf-8') | |||
|
45 | ||||
|
46 | ||||
|
47 | def sysstr(s): | |||
|
48 | return s.decode('latin-1') | |||
|
49 | ||||
|
50 | ||||
|
51 | piped_stdout = os.pipe2(os.O_NONBLOCK | os.O_CLOEXEC) | |||
|
52 | piped_stderr = os.pipe2(os.O_NONBLOCK | os.O_CLOEXEC) | |||
|
53 | ||||
|
54 | stdout_writer = os.fdopen(piped_stdout[1], "rb") | |||
|
55 | stdout_reader = os.fdopen(piped_stdout[0], "rb") | |||
|
56 | stderr_writer = os.fdopen(piped_stderr[1], "rb") | |||
|
57 | stderr_reader = os.fdopen(piped_stderr[0], "rb") | |||
|
58 | ||||
|
59 | DEBUG_FILE = os.environ.get('SIGPIPE_REMOTE_DEBUG_FILE') | |||
|
60 | if DEBUG_FILE is None: | |||
|
61 | debug_stream = sys.stderr.buffer | |||
|
62 | else: | |||
|
63 | debug_stream = open(DEBUG_FILE, 'bw', buffering=0) | |||
|
64 | ||||
|
65 | SYNCFILE1 = os.environ.get('SYNCFILE1') | |||
|
66 | SYNCFILE2 = os.environ.get('SYNCFILE2') | |||
|
67 | if SYNCFILE1 is None: | |||
|
68 | print('SIGPIPE-HELPER: missing variable $SYNCFILE1', file=sys.stderr) | |||
|
69 | sys.exit(255) | |||
|
70 | if SYNCFILE2 is None: | |||
|
71 | print('SIGPIPE-HELPER: missing variable $SYNCFILE2', file=sys.stderr) | |||
|
72 | sys.exit(255) | |||
|
73 | ||||
|
74 | debug_stream.write(b'SIGPIPE-HELPER: Starting\n') | |||
|
75 | ||||
|
76 | TESTLIB_DIR = os.path.dirname(sys.argv[0]) | |||
|
77 | WAIT_SCRIPT = os.path.join(TESTLIB_DIR, 'wait-on-file') | |||
|
78 | ||||
|
79 | hooks_cmd = '%s 10 %s %s' | |||
|
80 | hooks_cmd %= ( | |||
|
81 | WAIT_SCRIPT, | |||
|
82 | SYNCFILE2, | |||
|
83 | SYNCFILE1, | |||
|
84 | ) | |||
|
85 | ||||
|
86 | cmd = ['hg'] | |||
|
87 | cmd += sys.argv[1:] | |||
|
88 | sub = subprocess.Popen( | |||
|
89 | cmd, | |||
|
90 | bufsize=0, | |||
|
91 | close_fds=True, | |||
|
92 | stdin=sys.stdin, | |||
|
93 | stdout=stdout_writer, | |||
|
94 | stderr=stderr_writer, | |||
|
95 | ) | |||
|
96 | ||||
|
97 | debug_stream.write(b'SIGPIPE-HELPER: Mercurial started\n') | |||
|
98 | ||||
|
99 | ||||
|
100 | shut_down = threading.Event() | |||
|
101 | ||||
|
102 | close_lock = threading.Lock() | |||
|
103 | ||||
|
104 | ||||
|
105 | def _read(stream): | |||
|
106 | try: | |||
|
107 | return stream.read() | |||
|
108 | except ValueError: | |||
|
109 | # read on closed file | |||
|
110 | return None | |||
|
111 | ||||
|
112 | ||||
|
113 | def forward_stdout(): | |||
|
114 | while not shut_down.is_set(): | |||
|
115 | c = _read(stdout_reader) | |||
|
116 | while c is not None: | |||
|
117 | sys.stdout.buffer.write(c) | |||
|
118 | c = _read(stdout_reader) | |||
|
119 | time.sleep(0.001) | |||
|
120 | with close_lock: | |||
|
121 | if not stdout_reader.closed: | |||
|
122 | stdout_reader.close() | |||
|
123 | debug_stream.write(b'SIGPIPE-HELPER: stdout closed\n') | |||
|
124 | ||||
|
125 | ||||
|
126 | def forward_stderr(): | |||
|
127 | while not shut_down.is_set(): | |||
|
128 | c = _read(stderr_reader) | |||
|
129 | if c is not None: | |||
|
130 | sys.stderr.buffer.write(c) | |||
|
131 | c = _read(stderr_reader) | |||
|
132 | time.sleep(0.001) | |||
|
133 | with close_lock: | |||
|
134 | if not stderr_reader.closed: | |||
|
135 | stderr_reader.close() | |||
|
136 | debug_stream.write(b'SIGPIPE-HELPER: stderr closed\n') | |||
|
137 | ||||
|
138 | ||||
|
139 | stdout_thread = threading.Thread(target=forward_stdout, daemon=True) | |||
|
140 | stderr_thread = threading.Thread(target=forward_stderr, daemon=True) | |||
|
141 | ||||
|
142 | try: | |||
|
143 | stdout_thread.start() | |||
|
144 | stderr_thread.start() | |||
|
145 | ||||
|
146 | debug_stream.write(b'SIGPIPE-HELPER: Redirection in place\n') | |||
|
147 | ||||
|
148 | try: | |||
|
149 | wait_file(sysbytes(SYNCFILE1)) | |||
|
150 | except RuntimeError as exc: | |||
|
151 | msg = sysbytes(str(exc)) | |||
|
152 | debug_stream.write(b'SIGPIPE-HELPER: wait failed: %s\n' % msg) | |||
|
153 | else: | |||
|
154 | debug_stream.write(b'SIGPIPE-HELPER: SYNCFILE1 detected\n') | |||
|
155 | with close_lock: | |||
|
156 | if not stdout_reader.closed: | |||
|
157 | stdout_reader.close() | |||
|
158 | if not stderr_reader.closed: | |||
|
159 | stderr_reader.close() | |||
|
160 | sys.stdin.close() | |||
|
161 | debug_stream.write(b'SIGPIPE-HELPER: pipes closed\n') | |||
|
162 | debug_stream.write(b'SIGPIPE-HELPER: creating SYNCFILE2\n') | |||
|
163 | write_file(sysbytes(SYNCFILE2)) | |||
|
164 | finally: | |||
|
165 | debug_stream.write(b'SIGPIPE-HELPER: Shutting down\n') | |||
|
166 | shut_down.set() | |||
|
167 | if not sys.stdin.closed: | |||
|
168 | sys.stdin.close() | |||
|
169 | try: | |||
|
170 | sub.wait(timeout=30) | |||
|
171 | except subprocess.TimeoutExpired: | |||
|
172 | msg = b'SIGPIPE-HELPER: Server process failed to terminate\n' | |||
|
173 | debug_stream.write(msg) | |||
|
174 | else: | |||
|
175 | debug_stream.write(b'SIGPIPE-HELPER: Server process terminated\n') | |||
|
176 | debug_stream.write(b'SIGPIPE-HELPER: Shut down\n') |
@@ -1,53 +1,30 b'' | |||||
1 | #require bash |
|
|||
2 |
|
|
1 | Test that, when an hg push is interrupted and the remote side receives SIGPIPE, | |
3 | the remote hg is able to successfully roll back the transaction. |
|
2 | the remote hg is able to successfully roll back the transaction. | |
4 |
|
3 | |||
5 | $ hg init -q remote |
|
4 | $ hg init -q remote | |
6 | $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" -q ssh://user@dummy/`pwd`/remote local |
|
5 | $ hg clone -e "\"$PYTHON\" \"$RUNTESTDIR/dummyssh\"" -q ssh://user@dummy/`pwd`/remote local | |
7 |
|
6 | $ SIGPIPE_REMOTE_DEBUG_FILE="$TESTTMP/DEBUGFILE" | ||
8 | $ pidfile=`pwd`/pidfile |
|
7 | $ SYNCFILE1="$TESTTMP/SYNCFILE1" | |
9 | $ >$pidfile |
|
8 | $ SYNCFILE2="$TESTTMP/SYNCFILE2" | |
10 |
|
9 | $ export SIGPIPE_REMOTE_DEBUG_FILE | ||
11 | $ script() { |
|
10 | $ export SYNCFILE1 | |
12 | > cat >"$1" |
|
11 | $ export SYNCFILE2 | |
13 | > chmod +x "$1" |
|
12 | $ PYTHONUNBUFFERED=1 | |
14 | > } |
|
13 | $ export PYTHONUNBUFFERED | |
15 |
|
14 | |||
16 | On the remote end, run hg, piping stdout and stderr through processes that we |
|
15 | On the remote end, run hg, piping stdout and stderr through processes that we | |
17 | know the PIDs of. We will later kill these to simulate an ssh client |
|
16 | know the PIDs of. We will later kill these to simulate an ssh client | |
18 | disconnecting. |
|
17 | disconnecting. | |
19 |
|
18 | |||
20 | $ killable_pipe=`pwd`/killable_pipe.sh |
|
19 | $ remotecmd="$RUNTESTDIR/testlib/sigpipe-remote.py" | |
21 | $ script $killable_pipe <<EOF |
|
|||
22 | > #!/usr/bin/env bash |
|
|||
23 | > echo \$\$ >> $pidfile |
|
|||
24 | > exec cat |
|
|||
25 | > EOF |
|
|||
26 |
|
||||
27 | $ remotecmd=`pwd`/remotecmd.sh |
|
|||
28 | $ script $remotecmd <<EOF |
|
|||
29 | > #!/usr/bin/env bash |
|
|||
30 | > hg "\$@" 1> >($killable_pipe) 2> >($killable_pipe >&2) |
|
|||
31 | > EOF |
|
|||
32 |
|
20 | |||
33 | In the pretxnchangegroup hook, kill the PIDs recorded above to simulate ssh |
|
21 | In the pretxnchangegroup hook, kill the PIDs recorded above to simulate ssh | |
34 | disconnecting. Then exit nonzero, to force a transaction rollback. |
|
22 | disconnecting. Then exit nonzero, to force a transaction rollback. | |
35 |
|
23 | |||
36 | $ hook_script=`pwd`/pretxnchangegroup.sh |
|
|||
37 | $ script $hook_script <<EOF |
|
|||
38 | > #!/usr/bin/env bash |
|
|||
39 | > for pid in \$(cat $pidfile) ; do |
|
|||
40 | > kill \$pid |
|
|||
41 | > while kill -0 \$pid 2>/dev/null ; do |
|
|||
42 | > sleep 0.1 |
|
|||
43 | > done |
|
|||
44 | > done |
|
|||
45 | > exit 1 |
|
|||
46 | > EOF |
|
|||
47 |
|
24 | |||
48 |
$ |
|
25 | $ cat >remote/.hg/hgrc <<EOF | |
49 | > [hooks] |
|
26 | > [hooks] | |
50 | > pretxnchangegroup.00-break-things=$hook_script |
|
27 | > pretxnchangegroup.00-break-things="$RUNTESTDIR/testlib/wait-on-file" 10 "$SYNCFILE2" "$SYNCFILE1" | |
51 |
> pretxnchangegroup.01-output-things=echo |
|
28 | > pretxnchangegroup.01-output-things=echo "some remote output to be forward to the closed pipe" | |
52 | > EOF |
|
29 | > EOF | |
53 |
|
30 | |||
@@ -55,8 +32,24 b' disconnecting. Then exit nonzero, to for' | |||||
55 |
|
|
32 | 000000000000 | |
56 |
$ cd |
|
33 | $ cd local | |
57 |
$ echo |
|
34 | $ echo foo > foo ; hg commit -qAm "commit" | |
58 |
$ hg |
|
35 | $ hg push -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --remotecmd "$remotecmd" | |
|
36 | pushing to ssh://user@dummy/$TESTTMP/remote | |||
|
37 | searching for changes | |||
|
38 | remote: adding changesets | |||
|
39 | remote: adding manifests | |||
|
40 | remote: adding file changes | |||
59 |
abort: |
|
41 | abort: stream ended unexpectedly (got 0 bytes, expected 4) | |
|
42 | [255] | |||
|
43 | $ cat $SIGPIPE_REMOTE_DEBUG_FILE | |||
|
44 | SIGPIPE-HELPER: Starting | |||
|
45 | SIGPIPE-HELPER: Mercurial started | |||
|
46 | SIGPIPE-HELPER: Redirection in place | |||
|
47 | SIGPIPE-HELPER: SYNCFILE1 detected | |||
|
48 | SIGPIPE-HELPER: pipes closed | |||
|
49 | SIGPIPE-HELPER: creating SYNCFILE2 | |||
|
50 | SIGPIPE-HELPER: Shutting down | |||
|
51 | SIGPIPE-HELPER: Server process terminated | |||
|
52 | SIGPIPE-HELPER: Shut down | |||
60 |
|
53 | |||
61 |
|
|
54 | The remote should be left in a good state | |
62 |
$ |
|
55 | $ hg --cwd ../remote tip -T '{node|short}\n' |
General Comments 0
You need to be logged in to leave comments.
Login now