Skip to content

Commit 8276b81

Browse files
committed
ci: Account for pre-existing children in process leak checks
1 parent 552819e commit 8276b81

File tree

1 file changed

+29
-7
lines changed

1 file changed

+29
-7
lines changed

tests/testlib.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11

2+
import errno
23
import logging
34
import os
45
import random
@@ -11,6 +12,7 @@
1112
import time
1213
import traceback
1314

15+
import psutil
1416
import unittest2
1517

1618
import mitogen.core
@@ -67,7 +69,6 @@ def get_fd_count():
6769
"""
6870
Return the number of FDs open by this process.
6971
"""
70-
import psutil
7172
return psutil.Process().num_fds()
7273

7374

@@ -334,6 +335,10 @@ def setUpClass(cls):
334335
# Broker() instantiations in setUp() etc.
335336
mitogen.fork.on_fork()
336337
cls._fd_count_before = get_fd_count()
338+
# Ignore children started by external packages - in particular
339+
# multiprocessing.resource_tracker.main()`, started when some Ansible
340+
# versions instantiate a `multithreading.Lock()`.
341+
cls._children_before = frozenset(psutil.Process().children())
337342
super(TestCase, cls).setUpClass()
338343

339344
ALLOWED_THREADS = set([
@@ -361,7 +366,10 @@ def _teardown_check_threads(self):
361366
def _teardown_check_fds(self):
362367
mitogen.core.Latch._on_fork()
363368
if get_fd_count() != self._fd_count_before:
364-
import os; os.system('lsof +E -w -p %s | grep -vw mem' % (os.getpid(),))
369+
if sys.platform == 'linux':
370+
os.system('lsof +E -w -p %i | grep -vw mem' % (os.getpid(),))
371+
else:
372+
os.system('lsof -w -p %i | grep -vw mem' % (os.getpid(),))
365373
assert 0, "%s leaked FDs. Count before: %s, after: %s" % (
366374
self, self._fd_count_before, get_fd_count(),
367375
)
@@ -374,19 +382,33 @@ def _teardown_check_zombies(self):
374382
if self.no_zombie_check:
375383
return
376384

385+
# pid=0: Wait for any child process in the same process group as us.
386+
# WNOHANG: Don't block if no processes ready to report status.
377387
try:
378388
pid, status = os.waitpid(0, os.WNOHANG)
379-
except OSError:
380-
return # ECHILD
389+
except OSError as e:
390+
# ECHILD: there are no child processes in our group.
391+
if e.errno == errno.ECHILD:
392+
return
393+
raise
381394

382395
if pid:
383396
assert 0, "%s failed to reap subprocess %d (status %d)." % (
384397
self, pid, status
385398
)
386399

387-
print('')
388-
print('Children of unit test process:')
389-
os.system('ps uww --ppid ' + str(os.getpid()))
400+
children_after = frozenset(psutil.Process().children())
401+
children_leaked = children_after.difference(self._children_before)
402+
if not children_leaked:
403+
return
404+
405+
print('Leaked children of unit test process:')
406+
os.system('ps -o "user,pid,%%cpu,%%mem,vsz,rss,tty,stat,start,time,command" -ww -p %s'
407+
% (','.join(str(p.pid) for p in children_leaked),))
408+
if self._children_before:
409+
print('Pre-existing children of unit test process:')
410+
os.system('ps -o "user,pid,%%cpu,%%mem,vsz,rss,tty,stat,start,time,command" -ww -p %s'
411+
% (','.join(str(p.pid) for p in self._children_before),))
390412
assert 0, "%s leaked still-running subprocesses." % (self,)
391413

392414
def tearDown(self):

0 commit comments

Comments
 (0)