Skip to content

fix: Reset task timer when calling progress.reset() #3711

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions examples/progress_reset_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""Example demonstrating the fix for progress bar timer reset issue #3273."""

import time
from rich.console import Console
from rich.progress import (
Progress,
TextColumn,
BarColumn,
TaskProgressColumn,
TimeElapsedColumn,
TimeRemainingColumn,
)


def main():
"""Demonstrate the fix for progress timer reset issue."""
console = Console()

console.print("[bold]Progress Bar Timer Reset Example[/bold]\n")
console.print("This example demonstrates the fix for issue #3273")
console.print("where restarting progress bar tasks did not restart their running clocks.\n")

with Progress(
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TaskProgressColumn(),
TimeElapsedColumn(),
TimeRemainingColumn(),
console=console,
) as progress:
# Simulate AI training scenario with epochs, training, and validation
epoch_task = progress.add_task("[red]Epochs", total=3)
train_task = progress.add_task("[green]Training", total=100)
valid_task = progress.add_task("[blue]Validation", total=50)

# Loop through epochs
for epoch in range(3):
console.print(f"\n[bold]Starting Epoch {epoch + 1}[/bold]")

# Training phase
for i in range(100):
progress.update(train_task, advance=1)
time.sleep(0.01)

# Show elapsed time before reset
train_task_obj = progress._tasks[train_task]
elapsed_before = train_task_obj.elapsed
console.print(f"Training elapsed time before reset: {elapsed_before:.2f} seconds")

# Stop training task
progress.stop_task(train_task)

# Validation phase
for i in range(50):
progress.update(valid_task, advance=1)
time.sleep(0.01)

# Stop validation task
progress.stop_task(valid_task)

# Complete one epoch
progress.update(epoch_task, advance=1)

# Reset tasks for next epoch (except on last epoch)
if epoch < 2:
console.print("\n[yellow]Resetting tasks for next epoch...[/yellow]")
progress.reset(train_task, start=True, completed=0)
progress.reset(valid_task, start=True, completed=0)

# Show elapsed time after reset
elapsed_after = train_task_obj.elapsed
console.print(f"Training elapsed time after reset: {elapsed_after:.4f} seconds")
console.print("[green]✓ Timer properly reset![/green]\n")
time.sleep(1) # Pause to show the reset

console.print("\n[bold green]Demo complete![/bold green]")
console.print("The timer now properly resets to 0 when tasks are reset.")


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions rich/progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -1501,6 +1501,7 @@ def reset(
task = self._tasks[task_id]
task._reset()
task.start_time = current_time if start else None
task.stop_time = None
if total is not None:
task.total = total
task.completed = completed
Expand Down
143 changes: 143 additions & 0 deletions tests/test_progress_reset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
"""Test that progress bar tasks properly reset their running clocks."""

import time
import pytest
from rich.console import Console
from rich.progress import Progress, Task, TaskID, TimeElapsedColumn


def test_progress_reset_restarts_clock():
"""Test that reset() properly resets the task's elapsed time."""
progress = Progress()

# Add a task and start it
task_id = progress.add_task("Test", total=100)
task = progress._tasks[task_id]

# Advance the task
progress.update(task_id, advance=50)

# Simulate some time passing
time.sleep(0.1)
elapsed_before_reset = task.elapsed
assert elapsed_before_reset > 0

# Reset the task
progress.reset(task_id)

# Check that start_time was reset
assert task.start_time is not None
assert task.stop_time is None
assert task.completed == 0
assert task.finished_time is None

# Check that elapsed time is very small (close to 0)
elapsed_after_reset = task.elapsed
assert elapsed_after_reset < elapsed_before_reset
assert elapsed_after_reset < 0.1 # Should be very small, accounting for test execution time


def test_progress_reset_with_start_false():
"""Test that reset() with start=False leaves start_time as None."""
progress = Progress()

# Add a task and start it
task_id = progress.add_task("Test", total=100)
task = progress._tasks[task_id]

# Simulate some work
progress.update(task_id, advance=50)
time.sleep(0.1)

# Reset without starting
progress.reset(task_id, start=False)

# Check that start_time is None
assert task.start_time is None
assert task.stop_time is None
assert task.elapsed is None


def test_progress_reset_clears_stop_time():
"""Test that reset() clears stop_time to allow proper restart."""
progress = Progress()

# Add a task and start it
task_id = progress.add_task("Test", total=100)
task = progress._tasks[task_id]

# Stop the task
progress.stop_task(task_id)
assert task.stop_time is not None

# Reset the task
progress.reset(task_id)

# Check that stop_time was cleared
assert task.stop_time is None
assert task.start_time is not None


def test_progress_reset_scenario():
"""Test a realistic scenario with multiple progress bars being reset."""
progress = Progress()

# Simulate the AI training scenario from the bug report
epoch_task = progress.add_task("Epochs", total=10)
train_task = progress.add_task("Training", total=1000)
valid_task = progress.add_task("Validation", total=500)

# Simulate first epoch
for _ in range(100):
progress.advance(train_task, 10)

progress.stop_task(train_task)

for _ in range(50):
progress.advance(valid_task, 10)

progress.stop_task(valid_task)
progress.advance(epoch_task, 1)

# Reset tasks for next epoch
progress.reset(train_task, start=True)
progress.reset(valid_task, start=False)

# Verify that training task has restarted properly
train_task_obj = progress._tasks[train_task]
assert train_task_obj.start_time is not None
assert train_task_obj.stop_time is None
assert train_task_obj.elapsed is not None
assert train_task_obj.elapsed < 0.1 # Should be very small

# Verify that validation task has not started
valid_task_obj = progress._tasks[valid_task]
assert valid_task_obj.start_time is None
assert valid_task_obj.stop_time is None
assert valid_task_obj.elapsed is None


def test_time_elapsed_column_after_reset():
"""Test that TimeElapsedColumn shows correct time after reset."""
progress = Progress()
column = TimeElapsedColumn()

# Add a task and let it run
task_id = progress.add_task("Test", total=100)
task = progress._tasks[task_id]

# Simulate work
progress.update(task_id, advance=50)
time.sleep(0.2)

# Get elapsed time display before reset
time_before = column.render(task)
assert str(time_before) != "-:--:--" # Should show actual time

# Reset the task
progress.reset(task_id)

# Get elapsed time display after reset
time_after = column.render(task)
# Should show very small time or 0:00:00
assert str(time_after) in ["0:00:00", "0:00:01"]