xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/multiprocessing/resource_tracker.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
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