##// END OF EJS Templates
procutil: avoid using os.fork() to implement runbgcommand...
procutil: avoid using os.fork() to implement runbgcommand We ran into the following deadlock: - some command creates an ssh peer, then raises without explicitly closing the peer (hg id + extension in our case) - dispatch catches the exception, calls ui.log('commandfinish', ..) (the sshpeer is still not closed), which calls logtoprocess, which calls procutil.runbgcommand. - in the child of runbgcommand's fork(), between the fork and the exec, the opening of file descriptors triggers a gc which runs the destructor for sshpeer, which waits on ssh's stderr being closed, which never happens since ssh's stderr is held open by the parent of the fork where said destructor hasn't run Remotefilelog appears to have a hack around this deadlock as well. I don't know if there's more subtlety to it, because even though the problem is determistic, it is very fragile, so I didn't manage to reduce it. I can imagine three ways of tackling this problem: 1. don't run any python between fork and exec in runbgcommand 2. make the finalizer harmless after the fork 3. close the peer without relying on gc behavior This commit goes with 1, as forking without exec'ing is tricky in general in a language with gc finalizers. And maybe it's better in the presence of rust threads. A future commit will try 2 or 3. Performance wise: at low memory usage, it's an improvement. At higher memory usage, it's about 2x faster than before when ensurestart=True, but 2x slower when ensurestart=False. Not sure if that matters. The reason for that last bit is that the subprocess.Popen always waits for the execve to finish, and at high memory usage, execve is slow because it deallocates the large page table. Numbers and script: before after mem=1.0GB, ensurestart=True 52.1ms 26.0ms mem=1.0GB, ensurestart=False 14.7ms 26.0ms mem=0.5GB, ensurestart=True 23.2ms 11.2ms mem=0.5GB, ensurestart=False 6.2ms 11.3ms mem=0.2GB, ensurestart=True 15.7ms 7.4ms mem=0.2GB, ensurestart=False 4.3ms 8.1ms mem=0.0GB, ensurestart=True 2.3ms 0.7ms mem=0.0GB, ensurestart=False 0.8ms 0.8ms import time for memsize in [1_000_000_000, 500_000_000, 250_000_000, 0]: mem = 'a' * memsize for ensurestart in [True, False]: now = time.time() n = 100 for i in range(n): procutil.runbgcommand([b'true'], {}, ensurestart=ensurestart) after = time.time() ms = (after - now) / float(n) * 1000 print(f'mem={memsize / 1e9:.1f}GB, ensurestart={ensurestart} -> {ms:.1f}ms') Differential Revision: https://phab.mercurial-scm.org/D9019

File last commit:

r47575:d4ba4d51 default
r47651:8759e22f default
Show More
server.py
235 lines | 7.0 KiB | text/x-python | PythonLexer
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 # server.py - utility and factory of server
#
Raphaël Gomès
contributor: change mentions of mpm to olivia...
r47575 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 #
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import
import os
from .i18n import _
Gregory Szorc
py3: manually import pycompat.open into files that need it...
r43355 from .pycompat import open
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506
from . import (
Yuya Nishihara
chgserver: make it a core module and drop extension flags...
r30513 chgserver,
Matt Harbison
serve: add support for Mercurial subrepositories...
r32005 cmdutil,
Yuya Nishihara
server: move service table and factory from commandserver...
r30507 commandserver,
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 error,
Yuya Nishihara
server: move service factory from hgweb
r30509 hgweb,
Augie Fackler
server: use pycompat to get argv
r32530 pycompat,
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 util,
)
Augie Fackler
formatting: blacken the codebase...
r43346 from .utils import procutil
Yuya Nishihara
procutil: bulk-replace util.std* to point to new module
r37137
Augie Fackler
formatting: blacken the codebase...
r43346 def runservice(
opts,
parentfn=None,
initfn=None,
runfn=None,
logfile=None,
runargs=None,
appendpid=False,
):
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 '''Run a command as a service.'''
Matt Harbison
server: refactor 'daemon_postexec' instructions into a dictionary
r37232 postexecargs = {}
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if opts[b'daemon_postexec']:
for inst in opts[b'daemon_postexec']:
if inst.startswith(b'unlink:'):
postexecargs[b'unlink'] = inst[7:]
elif inst.startswith(b'chdir:'):
postexecargs[b'chdir'] = inst[6:]
elif inst != b'none':
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.Abort(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b'invalid value for --daemon-postexec: %s') % inst
Augie Fackler
formatting: blacken the codebase...
r43346 )
Matt Harbison
server: refactor 'daemon_postexec' instructions into a dictionary
r37232
Matt Harbison
server: add an error feedback mechanism for when the daemon fails to launch...
r37229 # When daemonized on Windows, redirect stdout/stderr to the lockfile (which
# gets cleaned up after the child is up and running), so that the parent can
# read and print the error if this child dies early. See 594dd384803c. On
# other platforms, the child can write to the parent's stdio directly, until
# it is redirected prior to runfn().
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if pycompat.iswindows and opts[b'daemon_postexec']:
if b'unlink' in postexecargs and os.path.exists(
postexecargs[b'unlink']
):
Matt Harbison
server: refactor 'daemon_postexec' instructions into a dictionary
r37232 procutil.stdout.flush()
procutil.stderr.flush()
Matt Harbison
server: add an error feedback mechanism for when the daemon fails to launch...
r37229
Augie Fackler
formatting: blacken the codebase...
r43346 fd = os.open(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 postexecargs[b'unlink'], os.O_WRONLY | os.O_APPEND | os.O_BINARY
Augie Fackler
formatting: blacken the codebase...
r43346 )
Matt Harbison
server: refactor 'daemon_postexec' instructions into a dictionary
r37232 try:
Matt Harbison
server: minor code cleanup...
r37233 os.dup2(fd, procutil.stdout.fileno())
os.dup2(fd, procutil.stderr.fileno())
Matt Harbison
server: refactor 'daemon_postexec' instructions into a dictionary
r37232 finally:
os.close(fd)
Matt Harbison
server: add an error feedback mechanism for when the daemon fails to launch...
r37229
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 def writepid(pid):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if opts[b'pid_file']:
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 if appendpid:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 mode = b'ab'
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 mode = b'wb'
fp = open(opts[b'pid_file'], mode)
fp.write(b'%d\n' % pid)
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 fp.close()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if opts[b'daemon'] and not opts[b'daemon_postexec']:
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 # Signal child process startup with file removal
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 lockfd, lockpath = pycompat.mkstemp(prefix=b'hg-service-')
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 os.close(lockfd)
try:
if not runargs:
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 runargs = procutil.hgcmd() + pycompat.sysargv[1:]
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 runargs.append(b'--daemon-postexec=unlink:%s' % lockpath)
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 # Don't pass --cwd to the child process, because we've already
# changed directory.
Gregory Szorc
global: use pycompat.xrange()...
r38806 for i in pycompat.xrange(1, len(runargs)):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if runargs[i].startswith(b'--cwd='):
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 del runargs[i]
break
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif runargs[i].startswith(b'--cwd'):
Augie Fackler
formatting: blacken the codebase...
r43346 del runargs[i : i + 2]
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 break
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 def condfn():
return not os.path.exists(lockpath)
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 pid = procutil.rundetached(runargs, condfn)
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 if pid < 0:
Matt Harbison
server: add an error feedback mechanism for when the daemon fails to launch...
r37229 # If the daemonized process managed to write out an error msg,
# report it.
if pycompat.iswindows and os.path.exists(lockpath):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 with open(lockpath, b'rb') as log:
Matt Harbison
server: add an error feedback mechanism for when the daemon fails to launch...
r37229 for line in log:
procutil.stderr.write(line)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(b'child process failed to start'))
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 writepid(pid)
finally:
Ryan McElroy
server: use tryunlink
r31548 util.tryunlink(lockpath)
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 if parentfn:
return parentfn(pid)
else:
return
if initfn:
initfn()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if not opts[b'daemon']:
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 writepid(procutil.getpid())
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if opts[b'daemon_postexec']:
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 try:
os.setsid()
except AttributeError:
pass
Matt Harbison
server: add an error feedback mechanism for when the daemon fails to launch...
r37229
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if b'chdir' in postexecargs:
os.chdir(postexecargs[b'chdir'])
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 procutil.hidewindow()
Yuya Nishihara
procutil: bulk-replace util.std* to point to new module
r37137 procutil.stdout.flush()
procutil.stderr.flush()
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506
nullfd = os.open(os.devnull, os.O_RDWR)
logfilefd = nullfd
if logfile:
Augie Fackler
formatting: blacken the codebase...
r43346 logfilefd = os.open(
logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND, 0o666
)
Matt Harbison
server: minor code cleanup...
r37233 os.dup2(nullfd, procutil.stdin.fileno())
os.dup2(logfilefd, procutil.stdout.fileno())
os.dup2(logfilefd, procutil.stderr.fileno())
Augie Fackler
formatting: blacken the codebase...
r43346 stdio = (
procutil.stdin.fileno(),
procutil.stdout.fileno(),
procutil.stderr.fileno(),
)
Matt Harbison
server: minor code cleanup...
r37233 if nullfd not in stdio:
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 os.close(nullfd)
Matt Harbison
server: minor code cleanup...
r37233 if logfile and logfilefd not in stdio:
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 os.close(logfilefd)
Matt Harbison
server: add an error feedback mechanism for when the daemon fails to launch...
r37229 # Only unlink after redirecting stdout/stderr, so Windows doesn't
# complain about a sharing violation.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if b'unlink' in postexecargs:
os.unlink(postexecargs[b'unlink'])
Matt Harbison
server: add an error feedback mechanism for when the daemon fails to launch...
r37229
Yuya Nishihara
server: move cmdutil.service() to new module (API)...
r30506 if runfn:
return runfn()
Yuya Nishihara
server: move service table and factory from commandserver...
r30507
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
server: move service table and factory from commandserver...
r30507 _cmdservicemap = {
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'chgunix': chgserver.chgunixservice,
b'pipe': commandserver.pipeservice,
b'unix': commandserver.unixforkingservice,
Yuya Nishihara
server: move service table and factory from commandserver...
r30507 }
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
server: add public function to select either cmdserver or hgweb
r30510 def _createcmdservice(ui, repo, opts):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 mode = opts[b'cmdserver']
Yuya Nishihara
server: move service table and factory from commandserver...
r30507 try:
Yuya Nishihara
commandserver: enable logging when server process started...
r40858 servicefn = _cmdservicemap[mode]
Yuya Nishihara
server: move service table and factory from commandserver...
r30507 except KeyError:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(b'unknown mode %s') % mode)
Yuya Nishihara
commandserver: install logger to record server events through canonical API...
r40859 commandserver.setuplogging(ui, repo)
Yuya Nishihara
commandserver: enable logging when server process started...
r40858 return servicefn(ui, repo, opts)
Yuya Nishihara
server: move service factory from hgweb
r30509
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
server: add public function to select either cmdserver or hgweb
r30510 def _createhgwebservice(ui, repo, opts):
Yuya Nishihara
server: move service factory from hgweb
r30509 # this way we can check if something was given in the command-line
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if opts.get(b'port'):
opts[b'port'] = util.getport(opts.get(b'port'))
Yuya Nishihara
server: move service factory from hgweb
r30509
Martin von Zweigbergk
cleanup: use set literals...
r32291 alluis = {ui}
Yuya Nishihara
server: move service factory from hgweb
r30509 if repo:
baseui = repo.baseui
alluis.update([repo.baseui, repo.ui])
else:
baseui = ui
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 webconf = opts.get(b'web_conf') or opts.get(b'webdir_conf')
Yuya Nishihara
server: move service factory from hgweb
r30509 if webconf:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if opts.get(b'subrepos'):
raise error.Abort(_(b'--web-conf cannot be used with --subrepos'))
Matt Harbison
serve: add support for Mercurial subrepositories...
r32005
Yuya Nishihara
server: move service factory from hgweb
r30509 # load server settings (e.g. web.port) to "copied" ui, which allows
# hgwebdir to reload webconf cleanly
servui = ui.copy()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 servui.readconfig(webconf, sections=[b'web'])
Yuya Nishihara
server: move service factory from hgweb
r30509 alluis.add(servui)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif opts.get(b'subrepos'):
Matt Harbison
serve: add support for Mercurial subrepositories...
r32005 servui = ui
# If repo is None, hgweb.createapp() already raises a proper abort
# message as long as webconf is None.
if repo:
webconf = dict()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 cmdutil.addwebdirpath(repo, b"", webconf)
Yuya Nishihara
server: move service factory from hgweb
r30509 else:
servui = ui
Augie Fackler
formatting: blacken the codebase...
r43346 optlist = (
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b"name templates style address port prefix ipv6"
b" accesslog errorlog certificate encoding"
Augie Fackler
formatting: blacken the codebase...
r43346 )
Yuya Nishihara
server: move service factory from hgweb
r30509 for o in optlist.split():
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 val = opts.get(o, b'')
if val in (None, b''): # should check against default options instead
Yuya Nishihara
server: move service factory from hgweb
r30509 continue
for u in alluis:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 u.setconfig(b"web", o, val, b'serve')
Yuya Nishihara
server: move service factory from hgweb
r30509
app = hgweb.createapp(baseui, repo, webconf)
return hgweb.httpservice(servui, app, opts)
Yuya Nishihara
server: add public function to select either cmdserver or hgweb
r30510
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
server: add public function to select either cmdserver or hgweb
r30510 def createservice(ui, repo, opts):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if opts[b"cmdserver"]:
Yuya Nishihara
server: add public function to select either cmdserver or hgweb
r30510 return _createcmdservice(ui, repo, opts)
else:
return _createhgwebservice(ui, repo, opts)