Compute SHA256 with async I/O double-buffering for high-latency storage. Uses a producer-consumer pattern: a background thread reads file chunks into a bounded queue while the main thread computes the hash. This overlaps I/O latency with CPU computation.
(
file_path: Union[str, Path],
buffer_size_mb: int = 16,
tqdm_desc: Optional[str] = '[Calculating]',
disable_tqdm: Optional[bool] = True,
)
| 300 | |
| 301 | |
| 302 | def _get_file_hash_async( |
| 303 | file_path: Union[str, Path], |
| 304 | buffer_size_mb: int = 16, |
| 305 | tqdm_desc: Optional[str] = '[Calculating]', |
| 306 | disable_tqdm: Optional[bool] = True, |
| 307 | ) -> dict: |
| 308 | """Compute SHA256 with async I/O double-buffering for high-latency storage. |
| 309 | |
| 310 | Uses a producer-consumer pattern: a background thread reads file chunks |
| 311 | into a bounded queue while the main thread computes the hash. This |
| 312 | overlaps I/O latency with CPU computation. |
| 313 | """ |
| 314 | import queue |
| 315 | import threading |
| 316 | |
| 317 | from tqdm.auto import tqdm |
| 318 | |
| 319 | file_path = str(file_path) |
| 320 | declared_size = os.path.getsize(file_path) |
| 321 | buffer_size = buffer_size_mb * 1024 * 1024 |
| 322 | |
| 323 | chunk_queue = queue.Queue(maxsize=2) # Bounded to limit memory usage |
| 324 | read_error = [None] # Mutable container for thread error propagation |
| 325 | |
| 326 | def _producer(): |
| 327 | try: |
| 328 | with open(file_path, 'rb') as f: |
| 329 | while True: |
| 330 | chunk = f.read(buffer_size) |
| 331 | if not chunk: |
| 332 | break |
| 333 | chunk_queue.put(chunk) |
| 334 | except Exception as e: |
| 335 | read_error[0] = e |
| 336 | finally: |
| 337 | chunk_queue.put(None) # Sentinel to signal EOF |
| 338 | |
| 339 | reader_thread = threading.Thread(target=_producer, daemon=True) |
| 340 | reader_thread.start() |
| 341 | |
| 342 | file_hash = hashlib.sha256() |
| 343 | bytes_hashed = 0 |
| 344 | progress = tqdm( |
| 345 | total=declared_size, |
| 346 | desc=tqdm_desc, |
| 347 | disable=disable_tqdm, |
| 348 | dynamic_ncols=True, |
| 349 | unit='B', |
| 350 | unit_scale=True, |
| 351 | unit_divisor=1024, |
| 352 | ) |
| 353 | |
| 354 | while True: |
| 355 | chunk = chunk_queue.get() |
| 356 | if chunk is None: |
| 357 | break |
| 358 | file_hash.update(chunk) |
| 359 | bytes_hashed += len(chunk) |
no test coverage detected
searching dependent graphs…