Skip to content

Commit c315cea

Browse files
authored
Fix truncating file handle (#38425)
1 parent 98ca01f commit c315cea

2 files changed

Lines changed: 58 additions & 2 deletions

File tree

sdks/python/apache_beam/dataframe/io.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ def seekable(self):
515515

516516
@property
517517
def closed(self):
518-
return False
518+
return getattr(self._underlying, 'closed', False)
519519

520520
def __iter__(self):
521521
# For pandas is_file_like.
@@ -584,7 +584,18 @@ def _read(self, size=-1):
584584
return res
585585

586586
def flush(self):
587-
self._underlying.flush()
587+
if not self.closed:
588+
try:
589+
self._underlying.flush()
590+
except ValueError:
591+
pass
592+
593+
def close(self):
594+
if not self.closed and hasattr(self._underlying, 'close'):
595+
try:
596+
self._underlying.close()
597+
except (OSError, ValueError):
598+
pass
588599

589600

590601
class _ReadFromPandasDoFn(beam.DoFn, beam.RestrictionProvider):

sdks/python/apache_beam/dataframe/io_test.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,51 @@ def test_truncating_filehandle_iter(self):
296296
self._run_truncating_file_handle_iter_test('aaa b cccccccccccccccccccc')
297297
self._run_truncating_file_handle_iter_test('aaa b ccccccccccccccccc ')
298298

299+
def test_truncating_filehandle_flush_on_closed_stream(self):
300+
class ClosedFlushingStream(StringIO):
301+
def flush(self):
302+
if self.closed:
303+
raise ValueError("I/O operation on closed file.")
304+
super().flush()
305+
306+
s = 'a b c'
307+
tracker = restriction_trackers.OffsetRestrictionTracker(
308+
restriction_trackers.OffsetRange(0, len(s)))
309+
underlying = ClosedFlushingStream(s)
310+
handle = io._TruncatingFileHandle(
311+
underlying, tracker, splitter=io._DelimSplitter(' ', 10))
312+
313+
# Verify that calling flush() when the underlying stream is closed
314+
# succeeds without raising ValueError.
315+
underlying.close()
316+
handle.flush()
317+
handle.close()
318+
319+
def test_truncating_filehandle_exception_suppression(self):
320+
class FaultyStream(StringIO):
321+
@property
322+
def closed(self):
323+
return False
324+
325+
def flush(self):
326+
raise ValueError("Simulated flush error")
327+
328+
def close(self):
329+
raise OSError("Simulated close error")
330+
331+
s = 'a b c'
332+
tracker = restriction_trackers.OffsetRestrictionTracker(
333+
restriction_trackers.OffsetRange(0, len(s)))
334+
underlying = FaultyStream(s)
335+
handle = io._TruncatingFileHandle(
336+
underlying, tracker, splitter=io._DelimSplitter(' ', 10))
337+
338+
# Verify that ValueError raised during flush() is safely suppressed.
339+
handle.flush()
340+
341+
# Verify that OSError raised during close() is safely suppressed.
342+
handle.close()
343+
299344
@parameterized.expand([
300345
('defaults', {}),
301346
('header', dict(header=1)),

0 commit comments

Comments
 (0)