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

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: 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
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 class GClientChildrenSingleton(object):
327 _instance = None
328 children = {}
329 children_lock = threading.Lock()
330
331 def __new__(cls, *args, **kwargs):
332 if not isinstance(cls._instance, cls):
333 cls._instance = object.__new__(cls, *args, **kwargs)
M-A Ruel 2013/05/03 16:39:01 I don't think it's a good idea. Just create a glob
334 return cls._instance
335
336 def add(self, popen_obj):
337 with self.children_lock:
338 self.children[popen_obj.pid] = popen_obj
339
340 def remove(self, popen_obj):
341 with self.children_lock:
342 del self.children[popen_obj.pid]
343
344
345 def KillAllRemainingChildren():
346 children = GClientChildrenSingleton()
347 dead_objs = []
348
349 for pid, child in children.children.iteritems():
350 if child.poll():
351 dead_objs.append(child)
352 continue
353
354 try:
355 child.kill()
356 dead_objs.append(child)
357 except OSError:
358 pass
359
360 for obj in dead_objs:
361 children.remove(obj)
362
363 if children.children:
364 print >> sys.stderr, 'Could not kill the folling subprocesses:'
365 for pid in children.children:
366 print >> sys.stderr, ' ', pid
367
368
326 def CheckCallAndFilter(args, stdout=None, filter_fn=None, 369 def CheckCallAndFilter(args, stdout=None, filter_fn=None,
327 print_stdout=None, call_filter_on_first_line=False, 370 print_stdout=None, call_filter_on_first_line=False,
328 **kwargs): 371 **kwargs):
329 """Runs a command and calls back a filter function if needed. 372 """Runs a command and calls back a filter function if needed.
330 373
331 Accepts all subprocess2.Popen() parameters plus: 374 Accepts all subprocess2.Popen() parameters plus:
332 print_stdout: If True, the command's stdout is forwarded to stdout. 375 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 376 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 377 of the subprocess2's output. Each line has the trailing newline
335 character trimmed. 378 character trimmed.
336 stdout: Can be any bufferable output. 379 stdout: Can be any bufferable output.
337 380
338 stderr is always redirected to stdout. 381 stderr is always redirected to stdout.
339 """ 382 """
340 assert print_stdout or filter_fn 383 assert print_stdout or filter_fn
341 stdout = stdout or sys.stdout 384 stdout = stdout or sys.stdout
342 filter_fn = filter_fn or (lambda x: None) 385 filter_fn = filter_fn or (lambda x: None)
343 kid = subprocess2.Popen( 386 kid = subprocess2.Popen(
344 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT, 387 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT,
345 **kwargs) 388 **kwargs)
346 389
390 children = GClientChildrenSingleton()
391 children.add(kid)
392
347 # Do a flush of stdout before we begin reading from the subprocess2's stdout 393 # Do a flush of stdout before we begin reading from the subprocess2's stdout
348 stdout.flush() 394 stdout.flush()
349 395
350 # Also, we need to forward stdout to prevent weird re-ordering of output. 396 # 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: 397 # 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 398 # 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. 399 # end-of-line character is output after the prompt and it would not show up.
354 try: 400 try:
355 in_byte = kid.stdout.read(1) 401 in_byte = kid.stdout.read(1)
356 if in_byte: 402 if in_byte:
(...skipping 11 matching lines...) Expand all
368 in_line = '' 414 in_line = ''
369 else: 415 else:
370 filter_fn(in_line) 416 filter_fn(in_line)
371 in_line = '' 417 in_line = ''
372 in_byte = kid.stdout.read(1) 418 in_byte = kid.stdout.read(1)
373 # Flush the rest of buffered output. This is only an issue with 419 # Flush the rest of buffered output. This is only an issue with
374 # stdout/stderr not ending with a \n. 420 # stdout/stderr not ending with a \n.
375 if len(in_line): 421 if len(in_line):
376 filter_fn(in_line) 422 filter_fn(in_line)
377 rv = kid.wait() 423 rv = kid.wait()
424
425 # Don't put this in a 'finally,' since the child may still run if we get an
426 # exception.
427 children.remove(kid)
428
378 except KeyboardInterrupt: 429 except KeyboardInterrupt:
379 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args) 430 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args)
380 raise 431 raise
381 432
382 if rv: 433 if rv:
383 raise subprocess2.CalledProcessError( 434 raise subprocess2.CalledProcessError(
384 rv, args, kwargs.get('cwd', None), None, None) 435 rv, args, kwargs.get('cwd', None), None, None)
385 return 0 436 return 0
386 437
387 438
(...skipping 262 matching lines...) Expand 10 before | Expand all | Expand 10 after
650 701
651 class _Worker(threading.Thread): 702 class _Worker(threading.Thread):
652 """One thread to execute one WorkItem.""" 703 """One thread to execute one WorkItem."""
653 def __init__(self, item, index, args, kwargs): 704 def __init__(self, item, index, args, kwargs):
654 threading.Thread.__init__(self, name=item.name or 'Worker') 705 threading.Thread.__init__(self, name=item.name or 'Worker')
655 logging.info('_Worker(%s) reqs:%s' % (item.name, item.requirements)) 706 logging.info('_Worker(%s) reqs:%s' % (item.name, item.requirements))
656 self.item = item 707 self.item = item
657 self.index = index 708 self.index = index
658 self.args = args 709 self.args = args
659 self.kwargs = kwargs 710 self.kwargs = kwargs
711 self.daemon = True
660 712
661 def run(self): 713 def run(self):
662 """Runs in its own thread.""" 714 """Runs in its own thread."""
663 logging.debug('_Worker.run(%s)' % self.item.name) 715 logging.debug('_Worker.run(%s)' % self.item.name)
664 work_queue = self.kwargs['work_queue'] 716 work_queue = self.kwargs['work_queue']
665 try: 717 try:
666 self.item.run(*self.args, **self.kwargs) 718 self.item.run(*self.args, **self.kwargs)
719 except KeyboardInterrupt:
720 logging.info('Caught KeyboardInterrupt in thread %s' % self.item.name)
721 logging.info(str(sys.exc_info()))
722 work_queue.exceptions.put(sys.exc_info())
723 raise
M-A Ruel 2013/05/03 16:39:01 Then the lines 731-735 should be in a finally
667 except Exception: 724 except Exception:
668 # Catch exception location. 725 # Catch exception location.
669 logging.info('Caught exception in thread %s' % self.item.name) 726 logging.info('Caught exception in thread %s' % self.item.name)
670 logging.info(str(sys.exc_info())) 727 logging.info(str(sys.exc_info()))
671 work_queue.exceptions.put(sys.exc_info()) 728 work_queue.exceptions.put(sys.exc_info())
672 logging.info('_Worker.run(%s) done' % self.item.name) 729 logging.info('_Worker.run(%s) done' % self.item.name)
673 730
674 work_queue.ready_cond.acquire() 731 work_queue.ready_cond.acquire()
675 try: 732 try:
676 work_queue.ready_cond.notifyAll() 733 work_queue.ready_cond.notifyAll()
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after
764 821
765 Python on OSX 10.6 raises a NotImplementedError exception. 822 Python on OSX 10.6 raises a NotImplementedError exception.
766 """ 823 """
767 try: 824 try:
768 import multiprocessing 825 import multiprocessing
769 return multiprocessing.cpu_count() 826 return multiprocessing.cpu_count()
770 except: # pylint: disable=W0702 827 except: # pylint: disable=W0702
771 # Mac OS 10.6 only 828 # Mac OS 10.6 only
772 # pylint: disable=E1101 829 # pylint: disable=E1101
773 return int(os.sysconf('SC_NPROCESSORS_ONLN')) 830 return int(os.sysconf('SC_NPROCESSORS_ONLN'))
OLDNEW
« gclient.py ('K') | « gclient.py ('k') | tests/gclient_utils_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698