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

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: Fix nit. 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 = []
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 return bool(GCLIENT_CHILDREN)
360
361 @staticmethod
362 def KillAllRemainingChildren():
363 GClientChildren._attemptToKillChildren()
364
365 if GClientChildren._areZombies():
366 time.sleep(0.5)
367 GClientChildren._attemptToKillChildren()
368
369 with GCLIENT_CHILDREN_LOCK:
370 if GCLIENT_CHILDREN:
371 print >> sys.stderr, 'Could not kill the following subprocesses:'
372 for zombie in GCLIENT_CHILDREN:
373 print >> sys.stderr, ' ', zombie.pid
374
375
326 def CheckCallAndFilter(args, stdout=None, filter_fn=None, 376 def CheckCallAndFilter(args, stdout=None, filter_fn=None,
327 print_stdout=None, call_filter_on_first_line=False, 377 print_stdout=None, call_filter_on_first_line=False,
328 **kwargs): 378 **kwargs):
329 """Runs a command and calls back a filter function if needed. 379 """Runs a command and calls back a filter function if needed.
330 380
331 Accepts all subprocess2.Popen() parameters plus: 381 Accepts all subprocess2.Popen() parameters plus:
332 print_stdout: If True, the command's stdout is forwarded to stdout. 382 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 383 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 384 of the subprocess2's output. Each line has the trailing newline
335 character trimmed. 385 character trimmed.
336 stdout: Can be any bufferable output. 386 stdout: Can be any bufferable output.
337 387
338 stderr is always redirected to stdout. 388 stderr is always redirected to stdout.
339 """ 389 """
340 assert print_stdout or filter_fn 390 assert print_stdout or filter_fn
341 stdout = stdout or sys.stdout 391 stdout = stdout or sys.stdout
342 filter_fn = filter_fn or (lambda x: None) 392 filter_fn = filter_fn or (lambda x: None)
343 kid = subprocess2.Popen( 393 kid = subprocess2.Popen(
344 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT, 394 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT,
345 **kwargs) 395 **kwargs)
346 396
397 GClientChildren.add(kid)
398
347 # Do a flush of stdout before we begin reading from the subprocess2's stdout 399 # Do a flush of stdout before we begin reading from the subprocess2's stdout
348 stdout.flush() 400 stdout.flush()
349 401
350 # Also, we need to forward stdout to prevent weird re-ordering of output. 402 # 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: 403 # 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 404 # 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. 405 # end-of-line character is output after the prompt and it would not show up.
354 try: 406 try:
355 in_byte = kid.stdout.read(1) 407 in_byte = kid.stdout.read(1)
356 if in_byte: 408 if in_byte:
(...skipping 11 matching lines...) Expand all
368 in_line = '' 420 in_line = ''
369 else: 421 else:
370 filter_fn(in_line) 422 filter_fn(in_line)
371 in_line = '' 423 in_line = ''
372 in_byte = kid.stdout.read(1) 424 in_byte = kid.stdout.read(1)
373 # Flush the rest of buffered output. This is only an issue with 425 # Flush the rest of buffered output. This is only an issue with
374 # stdout/stderr not ending with a \n. 426 # stdout/stderr not ending with a \n.
375 if len(in_line): 427 if len(in_line):
376 filter_fn(in_line) 428 filter_fn(in_line)
377 rv = kid.wait() 429 rv = kid.wait()
430
431 # Don't put this in a 'finally,' since the child may still run if we get an
432 # exception.
433 GClientChildren.remove(kid)
434
378 except KeyboardInterrupt: 435 except KeyboardInterrupt:
379 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args) 436 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args)
380 raise 437 raise
381 438
382 if rv: 439 if rv:
383 raise subprocess2.CalledProcessError( 440 raise subprocess2.CalledProcessError(
384 rv, args, kwargs.get('cwd', None), None, None) 441 rv, args, kwargs.get('cwd', None), None, None)
385 return 0 442 return 0
386 443
387 444
(...skipping 262 matching lines...) Expand 10 before | Expand all | Expand 10 after
650 707
651 class _Worker(threading.Thread): 708 class _Worker(threading.Thread):
652 """One thread to execute one WorkItem.""" 709 """One thread to execute one WorkItem."""
653 def __init__(self, item, index, args, kwargs): 710 def __init__(self, item, index, args, kwargs):
654 threading.Thread.__init__(self, name=item.name or 'Worker') 711 threading.Thread.__init__(self, name=item.name or 'Worker')
655 logging.info('_Worker(%s) reqs:%s' % (item.name, item.requirements)) 712 logging.info('_Worker(%s) reqs:%s' % (item.name, item.requirements))
656 self.item = item 713 self.item = item
657 self.index = index 714 self.index = index
658 self.args = args 715 self.args = args
659 self.kwargs = kwargs 716 self.kwargs = kwargs
717 self.daemon = True
660 718
661 def run(self): 719 def run(self):
662 """Runs in its own thread.""" 720 """Runs in its own thread."""
663 logging.debug('_Worker.run(%s)' % self.item.name) 721 logging.debug('_Worker.run(%s)' % self.item.name)
664 work_queue = self.kwargs['work_queue'] 722 work_queue = self.kwargs['work_queue']
665 try: 723 try:
666 self.item.run(*self.args, **self.kwargs) 724 self.item.run(*self.args, **self.kwargs)
725 except KeyboardInterrupt:
726 logging.info('Caught KeyboardInterrupt in thread %s' % self.item.name)
727 logging.info(str(sys.exc_info()))
728 work_queue.exceptions.put(sys.exc_info())
729 raise
667 except Exception: 730 except Exception:
668 # Catch exception location. 731 # Catch exception location.
669 logging.info('Caught exception in thread %s' % self.item.name) 732 logging.info('Caught exception in thread %s' % self.item.name)
670 logging.info(str(sys.exc_info())) 733 logging.info(str(sys.exc_info()))
671 work_queue.exceptions.put(sys.exc_info()) 734 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: 735 finally:
678 work_queue.ready_cond.release() 736 logging.info('_Worker.run(%s) done' % self.item.name)
737 work_queue.ready_cond.acquire()
738 try:
739 work_queue.ready_cond.notifyAll()
740 finally:
741 work_queue.ready_cond.release()
679 742
680 743
681 def GetEditor(git, git_editor=None): 744 def GetEditor(git, git_editor=None):
682 """Returns the most plausible editor to use. 745 """Returns the most plausible editor to use.
683 746
684 In order of preference: 747 In order of preference:
685 - GIT_EDITOR/SVN_EDITOR environment variable 748 - GIT_EDITOR/SVN_EDITOR environment variable
686 - core.editor git configuration variable (if supplied by git-cl) 749 - core.editor git configuration variable (if supplied by git-cl)
687 - VISUAL environment variable 750 - VISUAL environment variable
688 - EDITOR environment variable 751 - EDITOR environment variable
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after
783 846
784 Python on OSX 10.6 raises a NotImplementedError exception. 847 Python on OSX 10.6 raises a NotImplementedError exception.
785 """ 848 """
786 try: 849 try:
787 import multiprocessing 850 import multiprocessing
788 return multiprocessing.cpu_count() 851 return multiprocessing.cpu_count()
789 except: # pylint: disable=W0702 852 except: # pylint: disable=W0702
790 # Mac OS 10.6 only 853 # Mac OS 10.6 only
791 # pylint: disable=E1101 854 # pylint: disable=E1101
792 return int(os.sysconf('SC_NPROCESSORS_ONLN')) 855 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