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

Side by Side Diff: gclient_utils.py

Issue 3342024: Add automatic auto-flushing stdout. (Closed)
Patch Set: update unit test Created 10 years, 3 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 2009 Google Inc. All Rights Reserved. 1 # Copyright 2009 Google Inc. All Rights Reserved.
2 # 2 #
3 # Licensed under the Apache License, Version 2.0 (the "License"); 3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License. 4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at 5 # You may obtain a copy of the License at
6 # 6 #
7 # http://www.apache.org/licenses/LICENSE-2.0 7 # http://www.apache.org/licenses/LICENSE-2.0
8 # 8 #
9 # Unless required by applicable law or agreed to in writing, software 9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, 10 # distributed under the License is distributed on an "AS IS" BASIS,
(...skipping 267 matching lines...) Expand 10 before | Expand all | Expand 10 after
278 % (' '.join(args), kwargs.get('cwd', '.'))) 278 % (' '.join(args), kwargs.get('cwd', '.')))
279 elif filter_fn: 279 elif filter_fn:
280 filter_fn(line) 280 filter_fn(line)
281 kwargs['filter_fn'] = filter_msg 281 kwargs['filter_fn'] = filter_msg
282 kwargs['call_filter_on_first_line'] = True 282 kwargs['call_filter_on_first_line'] = True
283 # Obviously. 283 # Obviously.
284 kwargs['print_stdout'] = True 284 kwargs['print_stdout'] = True
285 return CheckCallAndFilter(args, **kwargs) 285 return CheckCallAndFilter(args, **kwargs)
286 286
287 287
288 class StdoutAutoFlush(object):
289 """Automatically flush after N seconds."""
290 def __init__(self, stdout, delay=10):
291 self.lock = threading.Lock()
292 self.stdout = stdout
293 self.delay = delay
294 self.last_flushed_at = time.time()
295 self.stdout.flush()
296
297 def write(self, out):
298 """Thread-safe."""
299 self.stdout.write(out)
300 should_flush = False
301 with self.lock:
302 if (time.time() - self.last_flushed_at) > self.delay:
303 should_flush = True
304 self.last_flushed_at = time.time()
305 if should_flush:
306 self.stdout.flush()
307
308 def flush(self):
309 self.stdout.flush()
310
311
288 def CheckCallAndFilter(args, stdout=None, filter_fn=None, 312 def CheckCallAndFilter(args, stdout=None, filter_fn=None,
289 print_stdout=None, call_filter_on_first_line=False, 313 print_stdout=None, call_filter_on_first_line=False,
290 **kwargs): 314 **kwargs):
291 """Runs a command and calls back a filter function if needed. 315 """Runs a command and calls back a filter function if needed.
292 316
293 Accepts all subprocess.Popen() parameters plus: 317 Accepts all subprocess.Popen() parameters plus:
294 print_stdout: If True, the command's stdout is forwarded to stdout. 318 print_stdout: If True, the command's stdout is forwarded to stdout.
295 filter_fn: A function taking a single string argument called with each line 319 filter_fn: A function taking a single string argument called with each line
296 of the subprocess's output. Each line has the trailing newline 320 of the subprocess's output. Each line has the trailing newline
297 character trimmed. 321 character trimmed.
298 stdout: Can be any bufferable output. 322 stdout: Can be any bufferable output.
299 323
300 stderr is always redirected to stdout. 324 stderr is always redirected to stdout.
301 """ 325 """
302 assert print_stdout or filter_fn 326 assert print_stdout or filter_fn
303 stdout = stdout or sys.stdout 327 stdout = stdout or sys.stdout
304 filter_fn = filter_fn or (lambda x: None) 328 filter_fn = filter_fn or (lambda x: None)
305 assert not 'stderr' in kwargs 329 assert not 'stderr' in kwargs
306 kid = Popen(args, bufsize=0, 330 kid = Popen(args, bufsize=0,
307 stdout=subprocess.PIPE, stderr=subprocess.STDOUT, 331 stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
308 **kwargs) 332 **kwargs)
309 333
310 # Do a flush of stdout before we begin reading from the subprocess's stdout 334 # Do a flush of stdout before we begin reading from the subprocess's stdout
311 last_flushed_at = time.time()
312 stdout.flush() 335 stdout.flush()
313 336
314 # Also, we need to forward stdout to prevent weird re-ordering of output. 337 # Also, we need to forward stdout to prevent weird re-ordering of output.
315 # This has to be done on a per byte basis to make sure it is not buffered: 338 # This has to be done on a per byte basis to make sure it is not buffered:
316 # normally buffering is done for each line, but if svn requests input, no 339 # normally buffering is done for each line, but if svn requests input, no
317 # end-of-line character is output after the prompt and it would not show up. 340 # end-of-line character is output after the prompt and it would not show up.
318 in_byte = kid.stdout.read(1) 341 in_byte = kid.stdout.read(1)
319 if in_byte: 342 if in_byte:
320 if call_filter_on_first_line: 343 if call_filter_on_first_line:
321 filter_fn(None) 344 filter_fn(None)
322 in_line = '' 345 in_line = ''
323 while in_byte: 346 while in_byte:
324 if in_byte != '\r': 347 if in_byte != '\r':
325 if print_stdout: 348 if print_stdout:
326 stdout.write(in_byte) 349 stdout.write(in_byte)
327 if in_byte != '\n': 350 if in_byte != '\n':
328 in_line += in_byte 351 in_line += in_byte
329 else: 352 else:
330 filter_fn(in_line) 353 filter_fn(in_line)
331 in_line = '' 354 in_line = ''
332 # Flush at least 10 seconds between line writes. We wait at least 10
333 # seconds to avoid overloading the reader that called us with output,
334 # which can slow busy readers down.
335 if (time.time() - last_flushed_at) > 10:
336 last_flushed_at = time.time()
337 stdout.flush()
338 in_byte = kid.stdout.read(1) 355 in_byte = kid.stdout.read(1)
339 # Flush the rest of buffered output. This is only an issue with 356 # Flush the rest of buffered output. This is only an issue with
340 # stdout/stderr not ending with a \n. 357 # stdout/stderr not ending with a \n.
341 if len(in_line): 358 if len(in_line):
342 filter_fn(in_line) 359 filter_fn(in_line)
343 rv = kid.wait() 360 rv = kid.wait()
344 if rv: 361 if rv:
345 raise CheckCallError(args, kwargs.get('cwd', None), rv, None) 362 raise CheckCallError(args, kwargs.get('cwd', None), rv, None)
346 return 0 363 return 0
347 364
(...skipping 216 matching lines...) Expand 10 before | Expand all | Expand 10 after
564 if exception: 581 if exception:
565 self.parent.exceptions.append(exception) 582 self.parent.exceptions.append(exception)
566 if self.parent.progress: 583 if self.parent.progress:
567 self.parent.progress.update(1) 584 self.parent.progress.update(1)
568 assert not self.item.name in self.parent.ran 585 assert not self.item.name in self.parent.ran
569 if not self.item.name in self.parent.ran: 586 if not self.item.name in self.parent.ran:
570 self.parent.ran.append(self.item.name) 587 self.parent.ran.append(self.item.name)
571 finally: 588 finally:
572 self.parent.ready_cond.notifyAll() 589 self.parent.ready_cond.notifyAll()
573 self.parent.ready_cond.release() 590 self.parent.ready_cond.release()
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