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

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