Chromium Code Reviews| Index: gclient_utils.py |
| diff --git a/gclient_utils.py b/gclient_utils.py |
| index e65893468469fed10c25c2c1f031d2075f8a7123..1981f36e70e07b26eb43052368d298b548915ea8 100644 |
| --- a/gclient_utils.py |
| +++ b/gclient_utils.py |
| @@ -217,124 +217,120 @@ def CheckCallAndFilterAndHeader(args, always=False, **kwargs): |
| return CheckCallAndFilter(args, **kwargs) |
| -def SoftClone(obj): |
| - """Clones an object. copy.copy() doesn't work on 'file' objects.""" |
| - if obj.__class__.__name__ == 'SoftCloned': |
| - return obj |
| - class SoftCloned(object): |
| - pass |
| - new_obj = SoftCloned() |
| - for member in dir(obj): |
| - if member.startswith('_'): |
| - continue |
| - setattr(new_obj, member, getattr(obj, member)) |
| - return new_obj |
| +class Wrapper(object): |
| + """Wraps an object, acting as a transparent proxy for all properties by |
| + default. |
| + """ |
| + def __init__(self, wrapped): |
| + self._wrapped = wrapped |
| + 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.""" |
| - if hasattr(fileobj, 'last_flushed_at'): |
| - # Already patched. Just update delay. |
| - fileobj.delay = delay |
| - return fileobj |
| + def __init__(self, wrapped, delay): |
| + super(AutoFlush, self).__init__(wrapped) |
| + if not hasattr(self, 'lock'): |
| + self.lock = threading.Lock() |
| + self.__last_flushed_at = time.time() |
| + self.delay = delay |
| + |
| + @property |
| + def autoflush(self): |
| + return self |
| - # Attribute 'XXX' defined outside __init__ |
| - # pylint: disable=W0201 |
| - 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) |
| + def write(self, out, *args, **kwargs): |
| + self._wrapped.write(out, *args, **kwargs) |
| should_flush = False |
| - new_fileobj.lock.acquire() |
| + self.lock.acquire() |
| try: |
| - if (new_fileobj.delay and |
| - (time.time() - new_fileobj.last_flushed_at) > new_fileobj.delay): |
| + if self.delay and (time.time() - self.__last_flushed_at) > self.delay: |
| should_flush = True |
| - new_fileobj.last_flushed_at = time.time() |
| + self.__last_flushed_at = time.time() |
| finally: |
|
Dirk Pranke
2011/10/21 21:14:48
Nit: what exceptions could be raised in here that
M-A Ruel
2011/10/22 01:36:00
Someone set sys.stdout.delay = None by error. If t
|
| - new_fileobj.lock.release() |
| + self.lock.release() |
| if should_flush: |
| - new_fileobj.flush() |
| - |
| - new_fileobj.write = auto_flush_write |
| - return new_fileobj |
| + self.flush() |
| -def MakeFileAnnotated(fileobj, include_zero=False): |
| +class Annotated(Wrapper): |
| """Creates a file object clone to automatically prepends every line in worker |
| - threads with a NN> prefix.""" |
| - if hasattr(fileobj, 'output_buffers'): |
| - # Already patched. |
| - return fileobj |
| + threads with a NN> prefix. |
| + """ |
| + def __init__(self, wrapped, include_zero=False): |
| + 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__ |
| - # pylint: disable=W0201 |
| - new_fileobj = SoftClone(fileobj) |
| - if not hasattr(new_fileobj, 'lock'): |
| - new_fileobj.lock = threading.Lock() |
| - new_fileobj.output_buffers = {} |
| - new_fileobj.old_annotated_write = new_fileobj.write |
| - |
| - def annotated_write(out): |
| - index = getattr(threading.currentThread(), 'index', None) |
| - if index is None: |
| - if not include_zero: |
| - # Unindexed threads aren't buffered. |
| - new_fileobj.old_annotated_write(out) |
| - return |
| - index = 0 |
| - |
| - new_fileobj.lock.acquire() |
| + @property |
| + def annotated(self): |
| + return self |
| + |
| + def write(self, out): |
| + index = getattr(threading.currentThread(), 'index', 0) |
| + if not index and not self.__include_zero: |
| + # Unindexed threads aren't buffered. |
| + return self._wrapped.write(out) |
| + |
| + self.lock.acquire() |
| try: |
| # 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 |
| # otherwise. Using an array is faster than using a dummy object. |
| - if not index in new_fileobj.output_buffers: |
| - obj = new_fileobj.output_buffers[index] = [''] |
| + if not index in self.__output_buffers: |
| + obj = self.__output_buffers[index] = [''] |
| else: |
| - obj = new_fileobj.output_buffers[index] |
| + obj = self.__output_buffers[index] |
| finally: |
| - new_fileobj.lock.release() |
| + self.lock.release() |
| # Continue lockless. |
| obj[0] += out |
| while '\n' in obj[0]: |
| line, remaining = obj[0].split('\n', 1) |
| if line: |
| - new_fileobj.old_annotated_write('%d>%s\n' % (index, line)) |
| + self._wrapped.write('%d>%s\n' % (index, line)) |
| obj[0] = remaining |
| - def full_flush(): |
| + def flush(self): |
| """Flush buffered output.""" |
| orphans = [] |
| - new_fileobj.lock.acquire() |
| + self.lock.acquire() |
| try: |
| # Detect threads no longer existing. |
| indexes = (getattr(t, 'index', None) for t in threading.enumerate()) |
| indexes = filter(None, indexes) |
| - for index in new_fileobj.output_buffers: |
| + for index in self.__output_buffers: |
| 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: |
| - del new_fileobj.output_buffers[orphan[0]] |
| + del self.__output_buffers[orphan[0]] |
| finally: |
| - new_fileobj.lock.release() |
| + self.lock.release() |
| # Don't keep the lock while writting. Will append \n when it shouldn't. |
| for orphan in orphans: |
| 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 |
| - new_fileobj.full_flush = full_flush |
| - return new_fileobj |
| +def MakeFileAutoFlush(fileobj, delay=10): |
| + autoflush = getattr(fileobj, 'autoflush', None) |
| + 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, |
| @@ -638,7 +634,7 @@ class ExecutionQueue(object): |
| self.running.append(t) |
| else: |
| t.join() |
| - sys.stdout.full_flush() # pylint: disable=E1101 |
| + sys.stdout.flush() |
| if self.progress: |
| self.progress.update(1, t.item.name) |
| if t.item.name in self.ran: |