view hgdemandimport/tracing.py @ 44781:ed684a82e29b stable

procutil: always waiting on child processes to prevent zombies with 'hg serve' When runbgcommand is invoked by an extension with ensurestart=False, we never called waitpid - which is fine in most cases, except if that's happening on a command server (e.g. chg), in which case the child defunct process will just sit there for as long as the server is running. The actual semantics of SIGCHLD signal handling is a lot more complex than it seems, and the POSIX standard *seems* to read that it's ignored by default and everything would just work without the waitpid if we're not listening for it, but the truth is that it's only ignored if we *explicitly* set it to SIG_IGN. We further cannot set it to SIG_IGN or to a catch-all handler across all of 'hg serve', because Python's suprocess.Popen relies on that signal, and a few specific parts of hg also set custom handlers, so instead we wait for specific PIDs in dedicated threads. I did a poor-man's benchmark of the thread creation and it seems to take about 1ms, which is way better than the 20+ms from ensurestart=True. Differential Revision: https://phab.mercurial-scm.org/D8497
author Rodrigo Damazio Bovendorp <rdamazio@google.com>
date Thu, 07 May 2020 03:14:52 -0700
parents 2372284d9457
children 6000f5b25c9b
line wrap: on
line source

# Support code for event tracing in Mercurial. Lives in demandimport
# so it can also be used in demandimport.
#
# Copyright 2018 Google LLC.
#
# 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 contextlib
import os

_pipe = None
_checked = False
_session = 'none'


def _isactive():
    global _pipe, _session, _checked
    if _pipe is None:
        if _checked:
            return False
        _checked = True
        if 'HGCATAPULTSERVERPIPE' not in os.environ:
            return False
        _pipe = open(os.environ['HGCATAPULTSERVERPIPE'], 'w', 1)
        _session = os.environ.get('HGCATAPULTSESSION', 'none')
    return True


@contextlib.contextmanager
def log(whencefmt, *whenceargs):
    if not _isactive():
        yield
        return
    whence = whencefmt % whenceargs
    try:
        # Both writes to the pipe are wrapped in try/except to ignore
        # errors, as we can see mysterious errors in here if the pager
        # is active. Presumably other conditions could trigger
        # problems too.
        try:
            _pipe.write('START %s %s\n' % (_session, whence))
        except IOError:
            pass
        yield
    finally:
        try:
            _pipe.write('END %s %s\n' % (_session, whence))
        except IOError:
            pass


def counter(label, amount, *labelargs):
    if not _isactive():
        return
    l = label % labelargs
    # See above in log() for why this is in a try/except.
    try:
        _pipe.write('COUNTER %s %d %s\n' % (_session, amount, l))
    except IOError:
        pass