@@ -29,8 +29,12 @@
2929_HAVE_SIGMASK=hasattr(signal, 'pthread_sigmask')
3030_IGNORED_SIGNALS= (signal.SIGINT, signal.SIGTERM)
313132+defcleanup_noop(name):
33+raiseRuntimeError('noop should never be registered or cleaned up')
34+3235_CLEANUP_FUNCS= {
33-'noop': lambda: None,
36+'noop': cleanup_noop,
37+'dummy': lambdaname: None, # Dummy resource used in tests
3438}
35393640ifos.name=='posix':
@@ -61,6 +65,7 @@ def __init__(self):
6165self._lock=threading.RLock()
6266self._fd=None
6367self._pid=None
68+self._exitcode=None
64696570def_reentrant_call_error(self):
6671# gh-109629: this happens if an explicit call to the ResourceTracker
@@ -84,9 +89,16 @@ def _stop(self):
8489os.close(self._fd)
8590self._fd=None
869187-os.waitpid(self._pid, 0)
92+_, status=os.waitpid(self._pid, 0)
93+8894self._pid=None
899596+try:
97+self._exitcode=os.waitstatus_to_exitcode(status)
98+exceptValueError:
99+# os.waitstatus_to_exitcode may raise an exception for invalid values
100+self._exitcode=None
101+90102defgetfd(self):
91103self.ensure_running()
92104returnself._fd
@@ -119,6 +131,7 @@ def ensure_running(self):
119131pass
120132self._fd=None
121133self._pid=None
134+self._exitcode=None
122135123136warnings.warn('resource_tracker: process died unexpectedly, '
124137'relaunching. Some resources might leak.')
@@ -221,6 +234,8 @@ def main(fd):
221234pass
222235223236cache= {rtype: set() forrtypein_CLEANUP_FUNCS.keys()}
237+exit_code=0
238+224239try:
225240# keep track of registered/unregistered resources
226241withopen(fd, 'rb') asf:
@@ -242,6 +257,7 @@ def main(fd):
242257else:
243258raiseRuntimeError('unrecognized command %r'%cmd)
244259exceptException:
260+exit_code=3
245261try:
246262sys.excepthook(*sys.exc_info())
247263except:
@@ -251,10 +267,17 @@ def main(fd):
251267forrtype, rtype_cacheincache.items():
252268ifrtype_cache:
253269try:
254-warnings.warn(
255-f'resource_tracker: There appear to be {len(rtype_cache)} '
256-f'leaked {rtype} objects to clean up at shutdown: {rtype_cache}'
257- )
270+exit_code=1
271+ifrtype=='dummy':
272+# The test 'dummy' resource is expected to leak.
273+# We skip the warning (and *only* the warning) for it.
274+pass
275+else:
276+warnings.warn(
277+f'resource_tracker: There appear to be '
278+f'{len(rtype_cache)} leaked {rtype} objects to '
279+f'clean up at shutdown: {rtype_cache}'
280+ )
258281exceptException:
259282pass
260283fornameinrtype_cache:
@@ -265,6 +288,9 @@ def main(fd):
265288try:
266289_CLEANUP_FUNCS[rtype](name)
267290exceptExceptionase:
291+exit_code=2
268292warnings.warn('resource_tracker: %r: %s'% (name, e))
269293finally:
270294pass
295+296+sys.exit(exit_code)