Summary
azure.ai.ml._utils._asset_utils.upload_directory silently swallows worker upload failures when show_progress=False, so a directory upload can fail without raising, leaving a partial/incomplete upload in remote storage while callers proceed as if it succeeded.
Package
- Name: azure-ai-ml
- File:
sdk/ml/azure-ai-ml/azure/ai/ml/_utils/_asset_utils.py
- Function:
upload_directory
Root cause
Files are uploaded via a ThreadPoolExecutor. Exceptions raised inside a worker (upload_file) are captured on the Future and only re-raised when future.result() is called. That call currently lives only inside the if show_progress: branch:
with ThreadPoolExecutor(max_workers=num_cores) as ex:
futures_dict = { ex.submit(upload_file, ...): (src, dest) for ... }
if show_progress:
with tqdm(...) as pbar:
for future in as_completed(futures_dict):
future.result() # only reached when show_progress=True
pbar.update(...)
# when show_progress=False, .result() is never called -> exceptions swallowed
When show_progress=False, no one calls future.result(). The executor's shutdown(wait=True) waits for the threads to finish, but the stored exceptions are never observed, so upload_directory returns normally as if the upload succeeded.
Expected behavior
Upload errors should propagate regardless of the show_progress value.
Actual behavior
With show_progress=False, upload errors are silently ignored and the function returns None (apparent success).
Fix
Observe all futures (call future.result()) even when progress is disabled. Fixed in PR #48242.
Summary
azure.ai.ml._utils._asset_utils.upload_directorysilently swallows worker upload failures whenshow_progress=False, so a directory upload can fail without raising, leaving a partial/incomplete upload in remote storage while callers proceed as if it succeeded.Package
sdk/ml/azure-ai-ml/azure/ai/ml/_utils/_asset_utils.pyupload_directoryRoot cause
Files are uploaded via a
ThreadPoolExecutor. Exceptions raised inside a worker (upload_file) are captured on theFutureand only re-raised whenfuture.result()is called. That call currently lives only inside theif show_progress:branch:When
show_progress=False, no one callsfuture.result(). The executor'sshutdown(wait=True)waits for the threads to finish, but the stored exceptions are never observed, soupload_directoryreturns normally as if the upload succeeded.Expected behavior
Upload errors should propagate regardless of the
show_progressvalue.Actual behavior
With
show_progress=False, upload errors are silently ignored and the function returnsNone(apparent success).Fix
Observe all futures (call
future.result()) even when progress is disabled. Fixed in PR #48242.