1*cda5da8dSAndroid Build Coastguard Worker############################################################################### 2*cda5da8dSAndroid Build Coastguard Worker# Server process to keep track of unlinked resources (like shared memory 3*cda5da8dSAndroid Build Coastguard Worker# segments, semaphores etc.) and clean them. 4*cda5da8dSAndroid Build Coastguard Worker# 5*cda5da8dSAndroid Build Coastguard Worker# On Unix we run a server process which keeps track of unlinked 6*cda5da8dSAndroid Build Coastguard Worker# resources. The server ignores SIGINT and SIGTERM and reads from a 7*cda5da8dSAndroid Build Coastguard Worker# pipe. Every other process of the program has a copy of the writable 8*cda5da8dSAndroid Build Coastguard Worker# end of the pipe, so we get EOF when all other processes have exited. 9*cda5da8dSAndroid Build Coastguard Worker# Then the server process unlinks any remaining resource names. 10*cda5da8dSAndroid Build Coastguard Worker# 11*cda5da8dSAndroid Build Coastguard Worker# This is important because there may be system limits for such resources: for 12*cda5da8dSAndroid Build Coastguard Worker# instance, the system only supports a limited number of named semaphores, and 13*cda5da8dSAndroid Build Coastguard Worker# shared-memory segments live in the RAM. If a python process leaks such a 14*cda5da8dSAndroid Build Coastguard Worker# resource, this resource will not be removed till the next reboot. Without 15*cda5da8dSAndroid Build Coastguard Worker# this resource tracker process, "killall python" would probably leave unlinked 16*cda5da8dSAndroid Build Coastguard Worker# resources. 17*cda5da8dSAndroid Build Coastguard Worker 18*cda5da8dSAndroid Build Coastguard Workerimport os 19*cda5da8dSAndroid Build Coastguard Workerimport signal 20*cda5da8dSAndroid Build Coastguard Workerimport sys 21*cda5da8dSAndroid Build Coastguard Workerimport threading 22*cda5da8dSAndroid Build Coastguard Workerimport warnings 23*cda5da8dSAndroid Build Coastguard Worker 24*cda5da8dSAndroid Build Coastguard Workerfrom . import spawn 25*cda5da8dSAndroid Build Coastguard Workerfrom . import util 26*cda5da8dSAndroid Build Coastguard Worker 27*cda5da8dSAndroid Build Coastguard Worker__all__ = ['ensure_running', 'register', 'unregister'] 28*cda5da8dSAndroid Build Coastguard Worker 29*cda5da8dSAndroid Build Coastguard Worker_HAVE_SIGMASK = hasattr(signal, 'pthread_sigmask') 30*cda5da8dSAndroid Build Coastguard Worker_IGNORED_SIGNALS = (signal.SIGINT, signal.SIGTERM) 31*cda5da8dSAndroid Build Coastguard Worker 32*cda5da8dSAndroid Build Coastguard Worker_CLEANUP_FUNCS = { 33*cda5da8dSAndroid Build Coastguard Worker 'noop': lambda: None, 34*cda5da8dSAndroid Build Coastguard Worker} 35*cda5da8dSAndroid Build Coastguard Worker 36*cda5da8dSAndroid Build Coastguard Workerif os.name == 'posix': 37*cda5da8dSAndroid Build Coastguard Worker import _multiprocessing 38*cda5da8dSAndroid Build Coastguard Worker import _posixshmem 39*cda5da8dSAndroid Build Coastguard Worker 40*cda5da8dSAndroid Build Coastguard Worker # Use sem_unlink() to clean up named semaphores. 41*cda5da8dSAndroid Build Coastguard Worker # 42*cda5da8dSAndroid Build Coastguard Worker # sem_unlink() may be missing if the Python build process detected the 43*cda5da8dSAndroid Build Coastguard Worker # absence of POSIX named semaphores. In that case, no named semaphores were 44*cda5da8dSAndroid Build Coastguard Worker # ever opened, so no cleanup would be necessary. 45*cda5da8dSAndroid Build Coastguard Worker if hasattr(_multiprocessing, 'sem_unlink'): 46*cda5da8dSAndroid Build Coastguard Worker _CLEANUP_FUNCS.update({ 47*cda5da8dSAndroid Build Coastguard Worker 'semaphore': _multiprocessing.sem_unlink, 48*cda5da8dSAndroid Build Coastguard Worker }) 49*cda5da8dSAndroid Build Coastguard Worker _CLEANUP_FUNCS.update({ 50*cda5da8dSAndroid Build Coastguard Worker 'shared_memory': _posixshmem.shm_unlink, 51*cda5da8dSAndroid Build Coastguard Worker }) 52*cda5da8dSAndroid Build Coastguard Worker 53*cda5da8dSAndroid Build Coastguard Worker 54*cda5da8dSAndroid Build Coastguard Workerclass ResourceTracker(object): 55*cda5da8dSAndroid Build Coastguard Worker 56*cda5da8dSAndroid Build Coastguard Worker def __init__(self): 57*cda5da8dSAndroid Build Coastguard Worker self._lock = threading.Lock() 58*cda5da8dSAndroid Build Coastguard Worker self._fd = None 59*cda5da8dSAndroid Build Coastguard Worker self._pid = None 60*cda5da8dSAndroid Build Coastguard Worker 61*cda5da8dSAndroid Build Coastguard Worker def _stop(self): 62*cda5da8dSAndroid Build Coastguard Worker with self._lock: 63*cda5da8dSAndroid Build Coastguard Worker if self._fd is None: 64*cda5da8dSAndroid Build Coastguard Worker # not running 65*cda5da8dSAndroid Build Coastguard Worker return 66*cda5da8dSAndroid Build Coastguard Worker 67*cda5da8dSAndroid Build Coastguard Worker # closing the "alive" file descriptor stops main() 68*cda5da8dSAndroid Build Coastguard Worker os.close(self._fd) 69*cda5da8dSAndroid Build Coastguard Worker self._fd = None 70*cda5da8dSAndroid Build Coastguard Worker 71*cda5da8dSAndroid Build Coastguard Worker os.waitpid(self._pid, 0) 72*cda5da8dSAndroid Build Coastguard Worker self._pid = None 73*cda5da8dSAndroid Build Coastguard Worker 74*cda5da8dSAndroid Build Coastguard Worker def getfd(self): 75*cda5da8dSAndroid Build Coastguard Worker self.ensure_running() 76*cda5da8dSAndroid Build Coastguard Worker return self._fd 77*cda5da8dSAndroid Build Coastguard Worker 78*cda5da8dSAndroid Build Coastguard Worker def ensure_running(self): 79*cda5da8dSAndroid Build Coastguard Worker '''Make sure that resource tracker process is running. 80*cda5da8dSAndroid Build Coastguard Worker 81*cda5da8dSAndroid Build Coastguard Worker This can be run from any process. Usually a child process will use 82*cda5da8dSAndroid Build Coastguard Worker the resource created by its parent.''' 83*cda5da8dSAndroid Build Coastguard Worker with self._lock: 84*cda5da8dSAndroid Build Coastguard Worker if self._fd is not None: 85*cda5da8dSAndroid Build Coastguard Worker # resource tracker was launched before, is it still running? 86*cda5da8dSAndroid Build Coastguard Worker if self._check_alive(): 87*cda5da8dSAndroid Build Coastguard Worker # => still alive 88*cda5da8dSAndroid Build Coastguard Worker return 89*cda5da8dSAndroid Build Coastguard Worker # => dead, launch it again 90*cda5da8dSAndroid Build Coastguard Worker os.close(self._fd) 91*cda5da8dSAndroid Build Coastguard Worker 92*cda5da8dSAndroid Build Coastguard Worker # Clean-up to avoid dangling processes. 93*cda5da8dSAndroid Build Coastguard Worker try: 94*cda5da8dSAndroid Build Coastguard Worker # _pid can be None if this process is a child from another 95*cda5da8dSAndroid Build Coastguard Worker # python process, which has started the resource_tracker. 96*cda5da8dSAndroid Build Coastguard Worker if self._pid is not None: 97*cda5da8dSAndroid Build Coastguard Worker os.waitpid(self._pid, 0) 98*cda5da8dSAndroid Build Coastguard Worker except ChildProcessError: 99*cda5da8dSAndroid Build Coastguard Worker # The resource_tracker has already been terminated. 100*cda5da8dSAndroid Build Coastguard Worker pass 101*cda5da8dSAndroid Build Coastguard Worker self._fd = None 102*cda5da8dSAndroid Build Coastguard Worker self._pid = None 103*cda5da8dSAndroid Build Coastguard Worker 104*cda5da8dSAndroid Build Coastguard Worker warnings.warn('resource_tracker: process died unexpectedly, ' 105*cda5da8dSAndroid Build Coastguard Worker 'relaunching. Some resources might leak.') 106*cda5da8dSAndroid Build Coastguard Worker 107*cda5da8dSAndroid Build Coastguard Worker fds_to_pass = [] 108*cda5da8dSAndroid Build Coastguard Worker try: 109*cda5da8dSAndroid Build Coastguard Worker fds_to_pass.append(sys.stderr.fileno()) 110*cda5da8dSAndroid Build Coastguard Worker except Exception: 111*cda5da8dSAndroid Build Coastguard Worker pass 112*cda5da8dSAndroid Build Coastguard Worker cmd = 'from multiprocessing.resource_tracker import main;main(%d)' 113*cda5da8dSAndroid Build Coastguard Worker r, w = os.pipe() 114*cda5da8dSAndroid Build Coastguard Worker try: 115*cda5da8dSAndroid Build Coastguard Worker fds_to_pass.append(r) 116*cda5da8dSAndroid Build Coastguard Worker # process will out live us, so no need to wait on pid 117*cda5da8dSAndroid Build Coastguard Worker exe = spawn.get_executable() 118*cda5da8dSAndroid Build Coastguard Worker args = [exe] + util._args_from_interpreter_flags() 119*cda5da8dSAndroid Build Coastguard Worker args += ['-c', cmd % r] 120*cda5da8dSAndroid Build Coastguard Worker # bpo-33613: Register a signal mask that will block the signals. 121*cda5da8dSAndroid Build Coastguard Worker # This signal mask will be inherited by the child that is going 122*cda5da8dSAndroid Build Coastguard Worker # to be spawned and will protect the child from a race condition 123*cda5da8dSAndroid Build Coastguard Worker # that can make the child die before it registers signal handlers 124*cda5da8dSAndroid Build Coastguard Worker # for SIGINT and SIGTERM. The mask is unregistered after spawning 125*cda5da8dSAndroid Build Coastguard Worker # the child. 126*cda5da8dSAndroid Build Coastguard Worker try: 127*cda5da8dSAndroid Build Coastguard Worker if _HAVE_SIGMASK: 128*cda5da8dSAndroid Build Coastguard Worker signal.pthread_sigmask(signal.SIG_BLOCK, _IGNORED_SIGNALS) 129*cda5da8dSAndroid Build Coastguard Worker pid = util.spawnv_passfds(exe, args, fds_to_pass) 130*cda5da8dSAndroid Build Coastguard Worker finally: 131*cda5da8dSAndroid Build Coastguard Worker if _HAVE_SIGMASK: 132*cda5da8dSAndroid Build Coastguard Worker signal.pthread_sigmask(signal.SIG_UNBLOCK, _IGNORED_SIGNALS) 133*cda5da8dSAndroid Build Coastguard Worker except: 134*cda5da8dSAndroid Build Coastguard Worker os.close(w) 135*cda5da8dSAndroid Build Coastguard Worker raise 136*cda5da8dSAndroid Build Coastguard Worker else: 137*cda5da8dSAndroid Build Coastguard Worker self._fd = w 138*cda5da8dSAndroid Build Coastguard Worker self._pid = pid 139*cda5da8dSAndroid Build Coastguard Worker finally: 140*cda5da8dSAndroid Build Coastguard Worker os.close(r) 141*cda5da8dSAndroid Build Coastguard Worker 142*cda5da8dSAndroid Build Coastguard Worker def _check_alive(self): 143*cda5da8dSAndroid Build Coastguard Worker '''Check that the pipe has not been closed by sending a probe.''' 144*cda5da8dSAndroid Build Coastguard Worker try: 145*cda5da8dSAndroid Build Coastguard Worker # We cannot use send here as it calls ensure_running, creating 146*cda5da8dSAndroid Build Coastguard Worker # a cycle. 147*cda5da8dSAndroid Build Coastguard Worker os.write(self._fd, b'PROBE:0:noop\n') 148*cda5da8dSAndroid Build Coastguard Worker except OSError: 149*cda5da8dSAndroid Build Coastguard Worker return False 150*cda5da8dSAndroid Build Coastguard Worker else: 151*cda5da8dSAndroid Build Coastguard Worker return True 152*cda5da8dSAndroid Build Coastguard Worker 153*cda5da8dSAndroid Build Coastguard Worker def register(self, name, rtype): 154*cda5da8dSAndroid Build Coastguard Worker '''Register name of resource with resource tracker.''' 155*cda5da8dSAndroid Build Coastguard Worker self._send('REGISTER', name, rtype) 156*cda5da8dSAndroid Build Coastguard Worker 157*cda5da8dSAndroid Build Coastguard Worker def unregister(self, name, rtype): 158*cda5da8dSAndroid Build Coastguard Worker '''Unregister name of resource with resource tracker.''' 159*cda5da8dSAndroid Build Coastguard Worker self._send('UNREGISTER', name, rtype) 160*cda5da8dSAndroid Build Coastguard Worker 161*cda5da8dSAndroid Build Coastguard Worker def _send(self, cmd, name, rtype): 162*cda5da8dSAndroid Build Coastguard Worker self.ensure_running() 163*cda5da8dSAndroid Build Coastguard Worker msg = '{0}:{1}:{2}\n'.format(cmd, name, rtype).encode('ascii') 164*cda5da8dSAndroid Build Coastguard Worker if len(msg) > 512: 165*cda5da8dSAndroid Build Coastguard Worker # posix guarantees that writes to a pipe of less than PIPE_BUF 166*cda5da8dSAndroid Build Coastguard Worker # bytes are atomic, and that PIPE_BUF >= 512 167*cda5da8dSAndroid Build Coastguard Worker raise ValueError('msg too long') 168*cda5da8dSAndroid Build Coastguard Worker nbytes = os.write(self._fd, msg) 169*cda5da8dSAndroid Build Coastguard Worker assert nbytes == len(msg), "nbytes {0:n} but len(msg) {1:n}".format( 170*cda5da8dSAndroid Build Coastguard Worker nbytes, len(msg)) 171*cda5da8dSAndroid Build Coastguard Worker 172*cda5da8dSAndroid Build Coastguard Worker 173*cda5da8dSAndroid Build Coastguard Worker_resource_tracker = ResourceTracker() 174*cda5da8dSAndroid Build Coastguard Workerensure_running = _resource_tracker.ensure_running 175*cda5da8dSAndroid Build Coastguard Workerregister = _resource_tracker.register 176*cda5da8dSAndroid Build Coastguard Workerunregister = _resource_tracker.unregister 177*cda5da8dSAndroid Build Coastguard Workergetfd = _resource_tracker.getfd 178*cda5da8dSAndroid Build Coastguard Worker 179*cda5da8dSAndroid Build Coastguard Workerdef main(fd): 180*cda5da8dSAndroid Build Coastguard Worker '''Run resource tracker.''' 181*cda5da8dSAndroid Build Coastguard Worker # protect the process from ^C and "killall python" etc 182*cda5da8dSAndroid Build Coastguard Worker signal.signal(signal.SIGINT, signal.SIG_IGN) 183*cda5da8dSAndroid Build Coastguard Worker signal.signal(signal.SIGTERM, signal.SIG_IGN) 184*cda5da8dSAndroid Build Coastguard Worker if _HAVE_SIGMASK: 185*cda5da8dSAndroid Build Coastguard Worker signal.pthread_sigmask(signal.SIG_UNBLOCK, _IGNORED_SIGNALS) 186*cda5da8dSAndroid Build Coastguard Worker 187*cda5da8dSAndroid Build Coastguard Worker for f in (sys.stdin, sys.stdout): 188*cda5da8dSAndroid Build Coastguard Worker try: 189*cda5da8dSAndroid Build Coastguard Worker f.close() 190*cda5da8dSAndroid Build Coastguard Worker except Exception: 191*cda5da8dSAndroid Build Coastguard Worker pass 192*cda5da8dSAndroid Build Coastguard Worker 193*cda5da8dSAndroid Build Coastguard Worker cache = {rtype: set() for rtype in _CLEANUP_FUNCS.keys()} 194*cda5da8dSAndroid Build Coastguard Worker try: 195*cda5da8dSAndroid Build Coastguard Worker # keep track of registered/unregistered resources 196*cda5da8dSAndroid Build Coastguard Worker with open(fd, 'rb') as f: 197*cda5da8dSAndroid Build Coastguard Worker for line in f: 198*cda5da8dSAndroid Build Coastguard Worker try: 199*cda5da8dSAndroid Build Coastguard Worker cmd, name, rtype = line.strip().decode('ascii').split(':') 200*cda5da8dSAndroid Build Coastguard Worker cleanup_func = _CLEANUP_FUNCS.get(rtype, None) 201*cda5da8dSAndroid Build Coastguard Worker if cleanup_func is None: 202*cda5da8dSAndroid Build Coastguard Worker raise ValueError( 203*cda5da8dSAndroid Build Coastguard Worker f'Cannot register {name} for automatic cleanup: ' 204*cda5da8dSAndroid Build Coastguard Worker f'unknown resource type {rtype}') 205*cda5da8dSAndroid Build Coastguard Worker 206*cda5da8dSAndroid Build Coastguard Worker if cmd == 'REGISTER': 207*cda5da8dSAndroid Build Coastguard Worker cache[rtype].add(name) 208*cda5da8dSAndroid Build Coastguard Worker elif cmd == 'UNREGISTER': 209*cda5da8dSAndroid Build Coastguard Worker cache[rtype].remove(name) 210*cda5da8dSAndroid Build Coastguard Worker elif cmd == 'PROBE': 211*cda5da8dSAndroid Build Coastguard Worker pass 212*cda5da8dSAndroid Build Coastguard Worker else: 213*cda5da8dSAndroid Build Coastguard Worker raise RuntimeError('unrecognized command %r' % cmd) 214*cda5da8dSAndroid Build Coastguard Worker except Exception: 215*cda5da8dSAndroid Build Coastguard Worker try: 216*cda5da8dSAndroid Build Coastguard Worker sys.excepthook(*sys.exc_info()) 217*cda5da8dSAndroid Build Coastguard Worker except: 218*cda5da8dSAndroid Build Coastguard Worker pass 219*cda5da8dSAndroid Build Coastguard Worker finally: 220*cda5da8dSAndroid Build Coastguard Worker # all processes have terminated; cleanup any remaining resources 221*cda5da8dSAndroid Build Coastguard Worker for rtype, rtype_cache in cache.items(): 222*cda5da8dSAndroid Build Coastguard Worker if rtype_cache: 223*cda5da8dSAndroid Build Coastguard Worker try: 224*cda5da8dSAndroid Build Coastguard Worker warnings.warn('resource_tracker: There appear to be %d ' 225*cda5da8dSAndroid Build Coastguard Worker 'leaked %s objects to clean up at shutdown' % 226*cda5da8dSAndroid Build Coastguard Worker (len(rtype_cache), rtype)) 227*cda5da8dSAndroid Build Coastguard Worker except Exception: 228*cda5da8dSAndroid Build Coastguard Worker pass 229*cda5da8dSAndroid Build Coastguard Worker for name in rtype_cache: 230*cda5da8dSAndroid Build Coastguard Worker # For some reason the process which created and registered this 231*cda5da8dSAndroid Build Coastguard Worker # resource has failed to unregister it. Presumably it has 232*cda5da8dSAndroid Build Coastguard Worker # died. We therefore unlink it. 233*cda5da8dSAndroid Build Coastguard Worker try: 234*cda5da8dSAndroid Build Coastguard Worker try: 235*cda5da8dSAndroid Build Coastguard Worker _CLEANUP_FUNCS[rtype](name) 236*cda5da8dSAndroid Build Coastguard Worker except Exception as e: 237*cda5da8dSAndroid Build Coastguard Worker warnings.warn('resource_tracker: %r: %s' % (name, e)) 238*cda5da8dSAndroid Build Coastguard Worker finally: 239*cda5da8dSAndroid Build Coastguard Worker pass 240