Skip to content

Commit

Permalink
Handle broken pipe errors
Browse files Browse the repository at this point in the history
If stdout is being piped to another process and that process
closes stdin, then we get an IOError with a broken pipe message.
If the reading end of the pipe closes the pipe, we should just exit.
This is a normal (and common) case and we shouldn't be printing
an error message when this happens.
  • Loading branch information
jamesls committed Jan 27, 2015
1 parent ecce9c6 commit ddec341
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 5 deletions.
20 changes: 15 additions & 5 deletions awscli/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ def _remove_request_id(self, response_data):
def _get_default_stream(self):
return compat.get_stdout_text_writer()

def _flush_stream(self, stream):
try:
stream.flush()
except IOError:
pass


class FullyBufferedFormatter(Formatter):
def __call__(self, operation, response, stream=None):
Expand All @@ -58,15 +64,19 @@ def __call__(self, operation, response, stream=None):
response_data = response.build_full_result()
else:
response_data = response
self._remove_request_id(response_data)
if self._args.query is not None:
response_data = self._args.query.search(response_data)
try:
self._remove_request_id(response_data)
if self._args.query is not None:
response_data = self._args.query.search(response_data)
self._format_response(operation, response_data, stream)
except IOError as e:
# If the reading end of our stdout stream has closed the file
# we can just exit.
pass
finally:
# flush is needed to avoid the "close failed in file object
# destructor" in python2.x (see http://bugs.python.org/issue11380).
stream.flush()
self._flush_stream(stream)


class JSONFormatter(FullyBufferedFormatter):
Expand Down Expand Up @@ -238,7 +248,7 @@ def __call__(self, operation, response, stream=None):
finally:
# flush is needed to avoid the "close failed in file object
# destructor" in python2.x (see http://bugs.python.org/issue11380).
stream.flush()
self._flush_stream(stream)

def _format_response(self, response, stream):
if self._args.query is not None:
Expand Down
16 changes: 16 additions & 0 deletions tests/unit/output/test_json_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import platform
import mock
from awscli.compat import six
from awscli.formatter import JSONFormatter

from awscli.testutils import BaseAWSCommandParamsTest, unittest
from awscli.compat import get_stdout_text_writer
Expand Down Expand Up @@ -95,3 +96,18 @@ def test_json_prints_unicode_chars(self):
# It should be encoded into the default encoding.
self.assertNotIn('\\u2713', output)
self.assertIn(expected, output)


class TestFormattersHandleClosedPipes(unittest.TestCase):
def test_fully_buffered_handles_io_error(self):
args = mock.Mock(query=None)
operation = mock.Mock(can_paginate=False)
response = '{"Foo": "Bar"}'
fake_closed_stream = mock.Mock(spec=six.StringIO)
fake_closed_stream.flush.side_effect = IOError
formatter = JSONFormatter(args)
formatter(operation, response, fake_closed_stream)
# We should not have let the IOError propogate, but
# we still should have called the flush() on the
# stream.
fake_closed_stream.flush.assert_called_with()

0 comments on commit ddec341

Please sign in to comment.