worker: rewrite error handling so os._exit covers all cases
Previously the worker error handling is like:
pid = os.fork() --+
if pid == 0: |
.... | problematic
.... --+
try: --+
.... | worker error handling
--+
If a signal arrives when Python is executing the "problematic" lines, an
external error handling (dispatch.py) will take over the control flow and
it's no longer guaranteed "os._exit" is called (see
86cd09bc13ba for why it
is necessary).
This patch rewrites the error handling so it covers all possible code paths
for a worker even during fork.
Note: "os.getpid() == parentpid" is used to test if the process is parent or
not intentionally, instead of checking "pid", because "pid = os.fork()" may
be not atomic - it's possible that that a signal hits the worker before the
assignment completes [1]. The newly added test replaces "os.fork" to
exercise that extreme case.
[1]: CPython compiles "pid = os.fork()" to 2 byte codes: "CALL_FUNCTION" and
"STORE_FAST", so it's probably not atomic:
def f():
pid = os.fork()
dis.dis(f)
2 0 LOAD_GLOBAL 0 (os)
3 LOAD_ATTR 1 (fork)
6 CALL_FUNCTION 0
9 STORE_FAST 0 (pid)
12 LOAD_CONST 0 (None)
15 RETURN_VALUE
from __future__ import absolute_import, print_function
import os
from mercurial import (
hg,
merge,
ui as uimod,
)
u = uimod.ui.load()
repo = hg.repository(u, 'test1', create=1)
os.chdir('test1')
def commit(text, time):
repo.commit(text=text, date="%d 0" % time)
def addcommit(name, time):
f = open(name, 'w')
f.write('%s\n' % name)
f.close()
repo[None].add([name])
commit(name, time)
def update(rev):
merge.update(repo, rev, False, True)
def merge_(rev):
merge.update(repo, rev, True, False)
if __name__ == '__main__':
addcommit("A", 0)
addcommit("B", 1)
update(0)
addcommit("C", 2)
merge_(1)
commit("D", 3)
update(2)
addcommit("E", 4)
addcommit("F", 5)
update(3)
addcommit("G", 6)
merge_(5)
commit("H", 7)
update(5)
addcommit("I", 8)
# Ancestors
print('Ancestors of 5')
for r in repo.changelog.ancestors([5]):
print(r, end=' ')
print('\nAncestors of 6 and 5')
for r in repo.changelog.ancestors([6, 5]):
print(r, end=' ')
print('\nAncestors of 5 and 4')
for r in repo.changelog.ancestors([5, 4]):
print(r, end=' ')
print('\nAncestors of 7, stop at 6')
for r in repo.changelog.ancestors([7], 6):
print(r, end=' ')
print('\nAncestors of 7, including revs')
for r in repo.changelog.ancestors([7], inclusive=True):
print(r, end=' ')
print('\nAncestors of 7, 5 and 3, including revs')
for r in repo.changelog.ancestors([7, 5, 3], inclusive=True):
print(r, end=' ')
# Descendants
print('\n\nDescendants of 5')
for r in repo.changelog.descendants([5]):
print(r, end=' ')
print('\nDescendants of 5 and 3')
for r in repo.changelog.descendants([5, 3]):
print(r, end=' ')
print('\nDescendants of 5 and 4')
print(*repo.changelog.descendants([5, 4]), sep=' ')