Skip to content

Commit 95944b6

Browse files
committed
fix: avoid ZeroDivisionError when a CloudFetch download takes no measurable time
ResultSetDownloadHandler.run() timed each download with time.time() and _log_download_metrics() divided the byte count by that duration. On Windows with Python 3.12 and earlier, time.time() only advances about every 15.6 ms, so a chunk that downloads within one tick has a duration of exactly 0 and the whole fetch fails with ZeroDivisionError from a logging call. Measure the download with time.perf_counter(), which is monotonic and high resolution, and report the speed as inf when the duration is not positive so logging can never fail a download. Signed-off-by: Maha Rana Yadavalli <271375718+maharanay22@users.noreply.github.com>
1 parent 61b9a7f commit 95944b6

3 files changed

Lines changed: 45 additions & 4 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Release History
22

3+
# Unreleased
4+
- Fix: a CloudFetch download that finished faster than the clock resolution (common on Windows) no longer fails the fetch with `ZeroDivisionError` while logging its download speed. Download time is now measured with `time.perf_counter()`.
5+
36
# 4.6.0 (2026-09-24)
47
- Upgrade Databricks SQL Kernel to 1.1.0; the kernel dependency is now stable and no longer experimental.
58
- Transparently auto-recover Thrift connections to Reyden / Real-Time warehouses: when a warehouse rejects the default Thrift protocol (SQLSTATE `KP001`), the session is re-opened on the kernel backend and the warehouse is remembered so later connections skip Thrift. Applies only when no backend was chosen explicitly.

‎src/databricks/sql/cloudfetch/downloader.py‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def run(self) -> DownloadedFile:
100100
self.link, self.settings.link_expiry_buffer_secs
101101
)
102102

103-
start_time = time.time()
103+
start_time = time.perf_counter()
104104

105105
with self._http_client.request_context(
106106
method=HttpMethod.GET,
@@ -113,7 +113,7 @@ def run(self) -> DownloadedFile:
113113
compressed_data = response.data
114114

115115
# Log download metrics
116-
download_duration = time.time() - start_time
116+
download_duration = time.perf_counter() - start_time
117117
self._log_download_metrics(
118118
self.link.fileLink, len(compressed_data), download_duration
119119
)
@@ -148,8 +148,13 @@ def _log_download_metrics(
148148
self, url: str, bytes_downloaded: int, duration_seconds: float
149149
):
150150
"""Log download speed metrics at INFO/WARN levels."""
151-
# Calculate speed in MB/s (ensure float division for precision)
152-
speed_mbps = (float(bytes_downloaded) / (1024 * 1024)) / duration_seconds
151+
# Calculate speed in MB/s (ensure float division for precision). A download
152+
# that finishes within the clock resolution has no measurable duration.
153+
speed_mbps = (
154+
(float(bytes_downloaded) / (1024 * 1024)) / duration_seconds
155+
if duration_seconds > 0
156+
else float("inf")
157+
)
153158

154159
urlEndpoint = url.split("?")[0]
155160
# INFO level logging

‎tests/unit/test_downloader.py‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,39 @@ def test_run_compressed_successful(self, mock_time):
179179
self.assertEqual(file.start_row_offset, result_link.startRowOffset)
180180
self.assertEqual(file.row_count, result_link.rowCount)
181181

182+
@patch("time.perf_counter", return_value=50.0)
183+
@patch("time.time", return_value=1000)
184+
def test_run_successful_when_clock_does_not_advance(
185+
self, mock_time, mock_perf_counter
186+
):
187+
# A download that finishes within one clock tick has a measured duration of 0
188+
mock_http_client = MagicMock()
189+
file_bytes = b"1234567890" * 10
190+
settings = Mock(link_expiry_buffer_secs=0, download_timeout=0, use_proxy=False)
191+
settings.is_lz4_compressed = False
192+
settings.min_cloudfetch_download_speed = 0.1
193+
result_link = Mock(expiryTime=1001, bytesNum=len(file_bytes))
194+
result_link.fileLink = "https://s3.amazonaws.com/bucket/file.arrow?token=xyz789"
195+
self._setup_mock_http_response(mock_http_client, status=200, data=file_bytes)
196+
197+
d = downloader.ResultSetDownloadHandler(
198+
settings,
199+
result_link,
200+
ssl_options=SSLOptions(),
201+
chunk_id=0,
202+
session_id_hex=Mock(),
203+
statement_id=Mock(),
204+
http_client=mock_http_client,
205+
)
206+
with self.assertLogs(downloader.logger, level="INFO") as logs:
207+
file = d.run()
208+
209+
self.assertEqual(file.file_bytes, file_bytes)
210+
self.assertEqual(file.start_row_offset, result_link.startRowOffset)
211+
self.assertEqual(file.row_count, result_link.rowCount)
212+
self.assertEqual([r.levelname for r in logs.records], ["INFO"])
213+
self.assertIn("CloudFetch download completed", logs.records[0].getMessage())
214+
182215
@patch("time.time", return_value=1000)
183216
def test_download_connection_error(self, mock_time):
184217
mock_http_client = MagicMock()

0 commit comments

Comments
 (0)