Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(341)

Side by Side Diff: gclient_utils.py

Issue 14759006: Kill subprocesses on KeyboardInterrupt. (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: Don't pop from a list as it's being iterated. Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « gclient.py ('k') | tests/gclient_utils_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """Generic utils.""" 5 """Generic utils."""
6 6
7 import codecs 7 import codecs
8 import logging 8 import logging
9 import os 9 import os
10 import Queue 10 import Queue
(...skipping 305 matching lines...) Expand 10 before | Expand all | Expand 10 after
316 return fileobj 316 return fileobj
317 return AutoFlush(fileobj, delay) 317 return AutoFlush(fileobj, delay)
318 318
319 319
320 def MakeFileAnnotated(fileobj, include_zero=False): 320 def MakeFileAnnotated(fileobj, include_zero=False):
321 if getattr(fileobj, 'annotated', None): 321 if getattr(fileobj, 'annotated', None):
322 return fileobj 322 return fileobj
323 return Annotated(fileobj) 323 return Annotated(fileobj)
324 324
325 325
326 GCLIENT_CHILDREN = []
Isaac (away) 2013/05/03 22:00:38 I think set() would be a better data structure.
327 GCLIENT_CHILDREN_LOCK = threading.Lock()
328
329
330 class GClientChildren(object):
331 @staticmethod
332 def add(popen_obj):
333 with GCLIENT_CHILDREN_LOCK:
334 GCLIENT_CHILDREN.append(popen_obj)
335
336 @staticmethod
337 def remove(popen_obj):
338 with GCLIENT_CHILDREN_LOCK:
339 GCLIENT_CHILDREN.remove(popen_obj)
340
341 @staticmethod
342 def _attemptToKillChildren():
343 global GCLIENT_CHILDREN
344 with GCLIENT_CHILDREN_LOCK:
345 zombies = [c for c in GCLIENT_CHILDREN if c.poll() is None]
346
347 for zombie in zombies:
348 try:
349 zombie.kill()
350 except OSError:
351 pass
352
353 with GCLIENT_CHILDREN_LOCK:
354 GCLIENT_CHILDREN = [k for k in GCLIENT_CHILDREN if k.poll() is not None]
355
356 @staticmethod
357 def _areZombies():
358 with GCLIENT_CHILDREN_LOCK:
359 if GCLIENT_CHILDREN:
M-A Ruel 2013/05/03 21:26:42 return bool(GCLIENT_CHILDREN)
Isaac (away) 2013/05/03 22:00:38 Should this filter based on k.poll()?
360 return True
361 return False
362
363 @staticmethod
364 def KillAllRemainingChildren():
365 GClientChildren._attemptToKillChildren()
366
367 if GClientChildren._areZombies():
368 time.sleep(0.5)
369 GClientChildren._attemptToKillChildren()
370
371 with GCLIENT_CHILDREN_LOCK:
372 if GCLIENT_CHILDREN:
373 print >> sys.stderr, 'Could not kill the following subprocesses:'
374 for zombie in GCLIENT_CHILDREN:
375 print >> sys.stderr, ' ', zombie.pid
376
377
326 def CheckCallAndFilter(args, stdout=None, filter_fn=None, 378 def CheckCallAndFilter(args, stdout=None, filter_fn=None,
327 print_stdout=None, call_filter_on_first_line=False, 379 print_stdout=None, call_filter_on_first_line=False,
328 **kwargs): 380 **kwargs):
329 """Runs a command and calls back a filter function if needed. 381 """Runs a command and calls back a filter function if needed.
330 382
331 Accepts all subprocess2.Popen() parameters plus: 383 Accepts all subprocess2.Popen() parameters plus:
332 print_stdout: If True, the command's stdout is forwarded to stdout. 384 print_stdout: If True, the command's stdout is forwarded to stdout.
333 filter_fn: A function taking a single string argument called with each line 385 filter_fn: A function taking a single string argument called with each line
334 of the subprocess2's output. Each line has the trailing newline 386 of the subprocess2's output. Each line has the trailing newline
335 character trimmed. 387 character trimmed.
336 stdout: Can be any bufferable output. 388 stdout: Can be any bufferable output.
337 389
338 stderr is always redirected to stdout. 390 stderr is always redirected to stdout.
339 """ 391 """
340 assert print_stdout or filter_fn 392 assert print_stdout or filter_fn
341 stdout = stdout or sys.stdout 393 stdout = stdout or sys.stdout
342 filter_fn = filter_fn or (lambda x: None) 394 filter_fn = filter_fn or (lambda x: None)
343 kid = subprocess2.Popen( 395 kid = subprocess2.Popen(
344 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT, 396 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT,
345 **kwargs) 397 **kwargs)
346 398
399 GClientChildren.add(kid)
400
347 # Do a flush of stdout before we begin reading from the subprocess2's stdout 401 # Do a flush of stdout before we begin reading from the subprocess2's stdout
348 stdout.flush() 402 stdout.flush()
349 403
350 # Also, we need to forward stdout to prevent weird re-ordering of output. 404 # Also, we need to forward stdout to prevent weird re-ordering of output.
351 # This has to be done on a per byte basis to make sure it is not buffered: 405 # This has to be done on a per byte basis to make sure it is not buffered:
352 # normally buffering is done for each line, but if svn requests input, no 406 # normally buffering is done for each line, but if svn requests input, no
353 # end-of-line character is output after the prompt and it would not show up. 407 # end-of-line character is output after the prompt and it would not show up.
354 try: 408 try:
355 in_byte = kid.stdout.read(1) 409 in_byte = kid.stdout.read(1)
356 if in_byte: 410 if in_byte:
(...skipping 11 matching lines...) Expand all
368 in_line = '' 422 in_line = ''
369 else: 423 else:
370 filter_fn(in_line) 424 filter_fn(in_line)
371 in_line = '' 425 in_line = ''
372 in_byte = kid.stdout.read(1) 426 in_byte = kid.stdout.read(1)
373 # Flush the rest of buffered output. This is only an issue with 427 # Flush the rest of buffered output. This is only an issue with
374 # stdout/stderr not ending with a \n. 428 # stdout/stderr not ending with a \n.
375 if len(in_line): 429 if len(in_line):
376 filter_fn(in_line) 430 filter_fn(in_line)
377 rv = kid.wait() 431 rv = kid.wait()
432
433 # Don't put this in a 'finally,' since the child may still run if we get an
434 # exception.
435 GClientChildren.remove(kid)
436
378 except KeyboardInterrupt: 437 except KeyboardInterrupt:
379 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args) 438 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args)
380 raise 439 raise
381 440
382 if rv: 441 if rv:
383 raise subprocess2.CalledProcessError( 442 raise subprocess2.CalledProcessError(
384 rv, args, kwargs.get('cwd', None), None, None) 443 rv, args, kwargs.get('cwd', None), None, None)
385 return 0 444 return 0
386 445
387 446
(...skipping 262 matching lines...) Expand 10 before | Expand all | Expand 10 after
650 709
651 class _Worker(threading.Thread): 710 class _Worker(threading.Thread):
652 """One thread to execute one WorkItem.""" 711 """One thread to execute one WorkItem."""
653 def __init__(self, item, index, args, kwargs): 712 def __init__(self, item, index, args, kwargs):
654 threading.Thread.__init__(self, name=item.name or 'Worker') 713 threading.Thread.__init__(self, name=item.name or 'Worker')
655 logging.info('_Worker(%s) reqs:%s' % (item.name, item.requirements)) 714 logging.info('_Worker(%s) reqs:%s' % (item.name, item.requirements))
656 self.item = item 715 self.item = item
657 self.index = index 716 self.index = index
658 self.args = args 717 self.args = args
659 self.kwargs = kwargs 718 self.kwargs = kwargs
719 self.daemon = True
Isaac (away) 2013/05/03 22:00:38 nice!
660 720
661 def run(self): 721 def run(self):
662 """Runs in its own thread.""" 722 """Runs in its own thread."""
663 logging.debug('_Worker.run(%s)' % self.item.name) 723 logging.debug('_Worker.run(%s)' % self.item.name)
664 work_queue = self.kwargs['work_queue'] 724 work_queue = self.kwargs['work_queue']
665 try: 725 try:
666 self.item.run(*self.args, **self.kwargs) 726 self.item.run(*self.args, **self.kwargs)
727 except KeyboardInterrupt:
728 logging.info('Caught KeyboardInterrupt in thread %s' % self.item.name)
Isaac (away) 2013/05/03 22:00:38 Use logger formatting, i.e.: logging.info('Caught
729 logging.info(str(sys.exc_info()))
730 work_queue.exceptions.put(sys.exc_info())
731 raise
667 except Exception: 732 except Exception:
668 # Catch exception location. 733 # Catch exception location.
669 logging.info('Caught exception in thread %s' % self.item.name) 734 logging.info('Caught exception in thread %s' % self.item.name)
670 logging.info(str(sys.exc_info())) 735 logging.info(str(sys.exc_info()))
671 work_queue.exceptions.put(sys.exc_info()) 736 work_queue.exceptions.put(sys.exc_info())
672 logging.info('_Worker.run(%s) done' % self.item.name)
673
674 work_queue.ready_cond.acquire()
675 try:
676 work_queue.ready_cond.notifyAll()
677 finally: 737 finally:
678 work_queue.ready_cond.release() 738 logging.info('_Worker.run(%s) done' % self.item.name)
Isaac (away) 2013/05/03 22:00:38 ditto, don't format in logging strings.
739 work_queue.ready_cond.acquire()
740 try:
741 work_queue.ready_cond.notifyAll()
742 finally:
743 work_queue.ready_cond.release()
679 744
680 745
681 def GetEditor(git, git_editor=None): 746 def GetEditor(git, git_editor=None):
682 """Returns the most plausible editor to use. 747 """Returns the most plausible editor to use.
683 748
684 In order of preference: 749 In order of preference:
685 - GIT_EDITOR/SVN_EDITOR environment variable 750 - GIT_EDITOR/SVN_EDITOR environment variable
686 - core.editor git configuration variable (if supplied by git-cl) 751 - core.editor git configuration variable (if supplied by git-cl)
687 - VISUAL environment variable 752 - VISUAL environment variable
688 - EDITOR environment variable 753 - EDITOR environment variable
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after
783 848
784 Python on OSX 10.6 raises a NotImplementedError exception. 849 Python on OSX 10.6 raises a NotImplementedError exception.
785 """ 850 """
786 try: 851 try:
787 import multiprocessing 852 import multiprocessing
788 return multiprocessing.cpu_count() 853 return multiprocessing.cpu_count()
789 except: # pylint: disable=W0702 854 except: # pylint: disable=W0702
790 # Mac OS 10.6 only 855 # Mac OS 10.6 only
791 # pylint: disable=E1101 856 # pylint: disable=E1101
792 return int(os.sysconf('SC_NPROCESSORS_ONLN')) 857 return int(os.sysconf('SC_NPROCESSORS_ONLN'))
OLDNEW
« no previous file with comments | « gclient.py ('k') | tests/gclient_utils_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698