This is an automated email from the ASF dual-hosted git repository.

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new e8dbbbe257 [python] Avoid thread-pool overhead for single-lane range 
reads (#10795)
e8dbbbe257 is described below

commit e8dbbbe2572227daef167cd3d2790973faa0c8a9
Author: XiaoHongbo <[email protected]>
AuthorDate: Sun Oct 25 10:45:46 2026 +0800

    [python] Avoid thread-pool overhead for single-lane range reads (#10795)
---
 paimon-python/pypaimon/common/file_io.py  |  7 ++--
 paimon-python/pypaimon/tests/blob_test.py | 53 +++++++++++++++++++++++++++++++
 2 files changed, 58 insertions(+), 2 deletions(-)

diff --git a/paimon-python/pypaimon/common/file_io.py 
b/paimon-python/pypaimon/common/file_io.py
index c2d132f7ac..455acd4379 100644
--- a/paimon-python/pypaimon/common/file_io.py
+++ b/paimon-python/pypaimon/common/file_io.py
@@ -404,8 +404,11 @@ class FileIO(ABC):
                 close_error = error
             return read_error, close_error
 
-        with ThreadPoolExecutor(len(lanes)) as pool:
-            outcomes = list(pool.map(_run_lane, lanes))
+        if len(lanes) == 1:
+            outcomes = [_run_lane(lanes[0])]
+        else:
+            with ThreadPoolExecutor(len(lanes)) as pool:
+                outcomes = list(pool.map(_run_lane, lanes))
         read_error = next(
             (error for error, _ in outcomes if error is not None), None)
         close_error = next(
diff --git a/paimon-python/pypaimon/tests/blob_test.py 
b/paimon-python/pypaimon/tests/blob_test.py
index b693b8d01f..9d0f5a660b 100644
--- a/paimon-python/pypaimon/tests/blob_test.py
+++ b/paimon-python/pypaimon/tests/blob_test.py
@@ -5972,6 +5972,59 @@ class CoalesceRangesTest(unittest.TestCase):
     """read_ranges_coalesced merges same-file adjacent reads into fewer 
requests
     (JingsongLi's IO-merging suggestion) while returning identical bytes."""
 
+    def test_single_lane_runs_without_executor(self):
+        from pypaimon.filesystem.local_file_io import LocalFileIO
+        from unittest.mock import patch
+
+        with tempfile.TemporaryDirectory() as directory:
+            path = os.path.join(directory, "data")
+            with open(path, "wb") as stream:
+                stream.write(b"0123456789")
+            file_io = LocalFileIO()
+            # Adjacent requests coalesce to one lane even with parallelism > 1.
+            for parallelism in (1, 8):
+                with self.subTest(parallelism=parallelism), patch(
+                        "concurrent.futures.ThreadPoolExecutor",
+                        side_effect=AssertionError("Unexpected executor")):
+                    ranges = [(path, 4, 2), (path, 2, 2)]
+                    self.assertEqual([b"45", b"23"],
+                                     file_io.read_ranges_coalesced(ranges, 
parallelism))
+                    views = file_io.read_ranges_coalesced_views(ranges, 
parallelism)
+                    self.assertEqual([b"45", b"23"], [bytes(view) for view in 
views])
+                    self.assertTrue(all(isinstance(view, memoryview) for view 
in views))
+                    with self.assertRaises(FileNotFoundError):
+                        file_io.read_ranges_coalesced(
+                            [(path + "-missing", 0, 1)], parallelism)
+
+    def test_single_lane_close_errors(self):
+        for parallelism in (1, 8):
+            for return_views in (False, True):
+                for fail_read in (False, True):
+                    with self.subTest(parallelism=parallelism,
+                                      return_views=return_views, 
fail_read=fail_read):
+                        read_error = IOError("read failed")
+                        close_error = IOError("close failed")
+                        stream = MagicMock(spec=["read_at", "close"])
+                        stream.read_at.return_value = b"2345"
+                        if fail_read:
+                            stream.read_at.side_effect = read_error
+                        stream.close.side_effect = close_error
+                        file_io = LocalFileIO()
+                        file_io.new_input_stream = 
MagicMock(return_value=stream)
+                        file_io.read_file_range = MagicMock()
+                        read = (file_io.read_ranges_coalesced_views if 
return_views
+                                else file_io.read_ranges_coalesced)
+                        # Both requests merge into one lane, regardless of 
parallelism.
+                        with patch("concurrent.futures.ThreadPoolExecutor",
+                                   side_effect=AssertionError("Unexpected 
executor")):
+                            with self.assertRaises(IOError) as raised:
+                                read([("blob", 4, 2), ("blob", 2, 2)], 
parallelism)
+                        self.assertIs(raised.exception,
+                                      read_error if fail_read else close_error)
+                        stream.read_at.assert_called_once_with(4, 2)
+                        stream.close.assert_called_once_with()
+                        file_io.read_file_range.assert_not_called()
+
     def test_coalesce_ranges_grouping(self):
         from pypaimon.common.file_io import _coalesce_ranges
         items = [(0, "a", 0, 10), (1, "a", 10, 10), (2, "a", 1000, 10), (3, 
"b", 0, 5)]

Reply via email to