Commit 042f0e73 authored by maruel@chromium.org's avatar maruel@chromium.org

Improve the sys.stdout proxy to be more transparent.

Otherwise isatty() could not be proxied correctly.

R=dpranke@chromium.org
BUG=
TEST=


Review URL: http://codereview.chromium.org/8370004

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@106864 0039d316-1c4b-4281-b951-d872f2087c98
parent d8aba2ce
...@@ -217,124 +217,120 @@ def CheckCallAndFilterAndHeader(args, always=False, **kwargs): ...@@ -217,124 +217,120 @@ def CheckCallAndFilterAndHeader(args, always=False, **kwargs):
return CheckCallAndFilter(args, **kwargs) return CheckCallAndFilter(args, **kwargs)
def SoftClone(obj): class Wrapper(object):
"""Clones an object. copy.copy() doesn't work on 'file' objects.""" """Wraps an object, acting as a transparent proxy for all properties by
if obj.__class__.__name__ == 'SoftCloned': default.
return obj """
class SoftCloned(object): def __init__(self, wrapped):
pass self._wrapped = wrapped
new_obj = SoftCloned()
for member in dir(obj):
if member.startswith('_'):
continue
setattr(new_obj, member, getattr(obj, member))
return new_obj
def __getattr__(self, name):
return getattr(self._wrapped, name)
def MakeFileAutoFlush(fileobj, delay=10):
class AutoFlush(Wrapper):
"""Creates a file object clone to automatically flush after N seconds.""" """Creates a file object clone to automatically flush after N seconds."""
if hasattr(fileobj, 'last_flushed_at'): def __init__(self, wrapped, delay):
# Already patched. Just update delay. super(AutoFlush, self).__init__(wrapped)
fileobj.delay = delay if not hasattr(self, 'lock'):
return fileobj self.lock = threading.Lock()
self.__last_flushed_at = time.time()
self.delay = delay
@property
def autoflush(self):
return self
# Attribute 'XXX' defined outside __init__ def write(self, out, *args, **kwargs):
# pylint: disable=W0201 self._wrapped.write(out, *args, **kwargs)
new_fileobj = SoftClone(fileobj)
if not hasattr(new_fileobj, 'lock'):
new_fileobj.lock = threading.Lock()
new_fileobj.last_flushed_at = time.time()
new_fileobj.delay = delay
new_fileobj.old_auto_flush_write = new_fileobj.write
# Silence pylint.
new_fileobj.flush = fileobj.flush
def auto_flush_write(out):
new_fileobj.old_auto_flush_write(out)
should_flush = False should_flush = False
new_fileobj.lock.acquire() self.lock.acquire()
try: try:
if (new_fileobj.delay and if self.delay and (time.time() - self.__last_flushed_at) > self.delay:
(time.time() - new_fileobj.last_flushed_at) > new_fileobj.delay):
should_flush = True should_flush = True
new_fileobj.last_flushed_at = time.time() self.__last_flushed_at = time.time()
finally: finally:
new_fileobj.lock.release() self.lock.release()
if should_flush: if should_flush:
new_fileobj.flush() self.flush()
new_fileobj.write = auto_flush_write
return new_fileobj
def MakeFileAnnotated(fileobj, include_zero=False): class Annotated(Wrapper):
"""Creates a file object clone to automatically prepends every line in worker """Creates a file object clone to automatically prepends every line in worker
threads with a NN> prefix.""" threads with a NN> prefix.
if hasattr(fileobj, 'output_buffers'): """
# Already patched. def __init__(self, wrapped, include_zero=False):
return fileobj super(Annotated, self).__init__(wrapped)
if not hasattr(self, 'lock'):
self.lock = threading.Lock()
self.__output_buffers = {}
self.__include_zero = include_zero
# Attribute 'XXX' defined outside __init__ @property
# pylint: disable=W0201 def annotated(self):
new_fileobj = SoftClone(fileobj) return self
if not hasattr(new_fileobj, 'lock'):
new_fileobj.lock = threading.Lock() def write(self, out):
new_fileobj.output_buffers = {} index = getattr(threading.currentThread(), 'index', 0)
new_fileobj.old_annotated_write = new_fileobj.write if not index and not self.__include_zero:
# Unindexed threads aren't buffered.
def annotated_write(out): return self._wrapped.write(out)
index = getattr(threading.currentThread(), 'index', None)
if index is None: self.lock.acquire()
if not include_zero:
# Unindexed threads aren't buffered.
new_fileobj.old_annotated_write(out)
return
index = 0
new_fileobj.lock.acquire()
try: try:
# Use a dummy array to hold the string so the code can be lockless. # Use a dummy array to hold the string so the code can be lockless.
# Strings are immutable, requiring to keep a lock for the whole dictionary # Strings are immutable, requiring to keep a lock for the whole dictionary
# otherwise. Using an array is faster than using a dummy object. # otherwise. Using an array is faster than using a dummy object.
if not index in new_fileobj.output_buffers: if not index in self.__output_buffers:
obj = new_fileobj.output_buffers[index] = [''] obj = self.__output_buffers[index] = ['']
else: else:
obj = new_fileobj.output_buffers[index] obj = self.__output_buffers[index]
finally: finally:
new_fileobj.lock.release() self.lock.release()
# Continue lockless. # Continue lockless.
obj[0] += out obj[0] += out
while '\n' in obj[0]: while '\n' in obj[0]:
line, remaining = obj[0].split('\n', 1) line, remaining = obj[0].split('\n', 1)
if line: if line:
new_fileobj.old_annotated_write('%d>%s\n' % (index, line)) self._wrapped.write('%d>%s\n' % (index, line))
obj[0] = remaining obj[0] = remaining
def full_flush(): def flush(self):
"""Flush buffered output.""" """Flush buffered output."""
orphans = [] orphans = []
new_fileobj.lock.acquire() self.lock.acquire()
try: try:
# Detect threads no longer existing. # Detect threads no longer existing.
indexes = (getattr(t, 'index', None) for t in threading.enumerate()) indexes = (getattr(t, 'index', None) for t in threading.enumerate())
indexes = filter(None, indexes) indexes = filter(None, indexes)
for index in new_fileobj.output_buffers: for index in self.__output_buffers:
if not index in indexes: if not index in indexes:
orphans.append((index, new_fileobj.output_buffers[index][0])) orphans.append((index, self.__output_buffers[index][0]))
for orphan in orphans: for orphan in orphans:
del new_fileobj.output_buffers[orphan[0]] del self.__output_buffers[orphan[0]]
finally: finally:
new_fileobj.lock.release() self.lock.release()
# Don't keep the lock while writting. Will append \n when it shouldn't. # Don't keep the lock while writting. Will append \n when it shouldn't.
for orphan in orphans: for orphan in orphans:
if orphan[1]: if orphan[1]:
new_fileobj.old_annotated_write('%d>%s\n' % (orphan[0], orphan[1])) self._wrapped.write('%d>%s\n' % (orphan[0], orphan[1]))
return self._wrapped.flush()
new_fileobj.write = annotated_write def MakeFileAutoFlush(fileobj, delay=10):
new_fileobj.full_flush = full_flush autoflush = getattr(fileobj, 'autoflush', None)
return new_fileobj if autoflush:
autoflush.delay = delay
return fileobj
return AutoFlush(fileobj, delay)
def MakeFileAnnotated(fileobj, include_zero=False):
if getattr(fileobj, 'annotated', None):
return fileobj
return Annotated(fileobj)
def CheckCallAndFilter(args, stdout=None, filter_fn=None, def CheckCallAndFilter(args, stdout=None, filter_fn=None,
...@@ -638,7 +634,7 @@ class ExecutionQueue(object): ...@@ -638,7 +634,7 @@ class ExecutionQueue(object):
self.running.append(t) self.running.append(t)
else: else:
t.join() t.join()
sys.stdout.full_flush() # pylint: disable=E1101 sys.stdout.flush()
if self.progress: if self.progress:
self.progress.update(1, t.item.name) self.progress.update(1, t.item.name)
if t.item.name in self.ran: if t.item.name in self.ran:
......
...@@ -28,13 +28,13 @@ class GclientUtilsUnittest(GclientUtilBase): ...@@ -28,13 +28,13 @@ class GclientUtilsUnittest(GclientUtilBase):
"""General gclient_utils.py tests.""" """General gclient_utils.py tests."""
def testMembersChanged(self): def testMembersChanged(self):
members = [ members = [
'CheckCallAndFilter', 'Annotated', 'AutoFlush', 'CheckCallAndFilter',
'CheckCallAndFilterAndHeader', 'Error', 'ExecutionQueue', 'FileRead', 'CheckCallAndFilterAndHeader', 'Error', 'ExecutionQueue', 'FileRead',
'FileWrite', 'FindFileUpwards', 'FindGclientRoot', 'FileWrite', 'FindFileUpwards', 'FindGclientRoot',
'GetGClientRootAndEntries', 'IsDateRevision', 'MakeDateRevision', 'GetGClientRootAndEntries', 'IsDateRevision', 'MakeDateRevision',
'MakeFileAutoFlush', 'MakeFileAnnotated', 'PathDifference', 'MakeFileAutoFlush', 'MakeFileAnnotated', 'PathDifference',
'PrintableObject', 'RemoveDirectory', 'SoftClone', 'SplitUrlRevision', 'PrintableObject', 'RemoveDirectory', 'SplitUrlRevision',
'SyntaxErrorToError', 'WorkItem', 'SyntaxErrorToError', 'Wrapper', 'WorkItem',
'errno', 'lockedmethod', 'logging', 'os', 'Queue', 're', 'rmtree', 'errno', 'lockedmethod', 'logging', 'os', 'Queue', 're', 'rmtree',
'safe_makedirs', 'stat', 'subprocess2', 'sys','threading', 'time', 'safe_makedirs', 'stat', 'subprocess2', 'sys','threading', 'time',
] ]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment