Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Deadlock when creating multiple progress bars and first one finishes #3190

Closed
2 tasks done
obendidi opened this issue Nov 6, 2023 · 4 comments
Closed
2 tasks done

Comments

@obendidi
Copy link

obendidi commented Nov 6, 2023

Describe the bug

Hello all,

Thanks for the great lib, I've been using for quite a bit of time, and it's always a pleasure to see the beautifull stuff I can make with it.

In one of my recent projects, I wanted to add progress bars to url downloads, I've adapted it from examples/downloader.py.

But the progress bars always seems to hang after the first/fastest one finishes, here the code with an example to reproduce:

import functools
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path

import httpx
from rich.progress import (
    BarColumn,
    DownloadColumn,
    Progress,
    TextColumn,
    TimeRemainingColumn,
    TransferSpeedColumn,
)


def _create_pbar(**kwargs) -> Progress:
    return Progress(
        TextColumn("[progress.description]{task.description}"),
        BarColumn(bar_width=None),
        "[progress.percentage]{task.percentage:>3.1f}%",
        "•",
        DownloadColumn(),
        "•",
        TransferSpeedColumn(),
        "•",
        TimeRemainingColumn(),
        **kwargs,
    )


def _download_url(
    url: str,
    save_dir: Path,
    overwrite: bool = False,
    progress: Progress | None = None,
    client: httpx.Client | None = None,
) -> Path:
    filename = Path(url).name
    save_path = save_dir / filename
    if save_path.is_file() and not overwrite:
        return save_path
    progress = progress or _create_pbar(disable=True)
    _streamer = client.stream if client else httpx.stream
    with open(save_path, "wb") as f:
        with _streamer("GET", url) as resp:
            total = int(resp.headers["Content-Length"])
            dl_task = progress.add_task(f"Download '{filename}'", total=total)
            with progress:
                for chunk in resp.iter_bytes():
                    f.write(chunk)
                    progress.update(dl_task, advance=len(chunk))
    return save_path


def download_urls(
    urls: list[str],
    save_dir: Path | str,
    overwrite: bool = False,
    max_workers: int | None = None,
    disable_pbar: bool = False,
) -> list[Path]:
    save_dir = Path(save_dir)
    progress = _create_pbar(disable=disable_pbar)
    client = httpx.Client()
    _worker = functools.partial(
        _download_url,
        save_dir=save_dir,
        overwrite=overwrite,
        progress=progress,
        client=client,
    )
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = [executor.submit(_worker, url) for url in urls]
        save_paths = list(f.result() for f in as_completed(futures))

    client.close()
    return save_paths


if __name__ == "__main__":
    urls = [
        "https://releases.ubuntu.com/focal/ubuntu-20.04.6-desktop-amd64.iso",
        "https://raw.githubusercontent.com/textualize/rich/master/imgs/logo.svg",
        "https://releases.ubuntu.com/22.04.3/ubuntu-22.04.3-desktop-amd64.iso",
    ]
    save_dir = "./data"
    download_urls(urls, save_dir, overwrite=True)

Usually when either the 1st or 2nd progress bar finishes the rest of the bars just hang, and look like this:

Download 'logo.svg' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 44.9/18.4 kB • 26.2 MB/s • 0:00:00
Download 'logo.svg'                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 44.9/18.4 kB • 26.2 MB/s • 0:00:00
Download 'ubuntu-22.04.3-desktop-amd64.iso' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╸━━━━━━━━━━━━━━━━━━━━━━━━ 78.5%  • 4.0/5.0 GB   • 15.1 MB/s • 0:01:12
Download 'ubuntu-20.04.6-desktop-amd64.iso' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 4.4/4.4 GB   • 12.5 MB/s • 0:00:00

(Here the second pbar is hanging, even thought the download is still ongoing in the background)

I'm probably doing something wrong, so would appreciate any help/advice you could provide. Thanks

Copy link

github-actions bot commented Nov 6, 2023

Thank you for your issue. Give us a little time to review it.

PS. You might want to check the FAQ if you haven't done so already.

This is an automated reply, generated by FAQtory

@Dobatymo
Copy link

Dobatymo commented Nov 9, 2023

You __enter__ progress multiple times (see with progress: in _download_url). You should only do that once (probably in download_urls).

@obendidi
Copy link
Author

Good catch, thanks 🙏

Copy link

I hope we solved your problem.

If you like using Rich, you might also enjoy Textual

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants