Skip to content

fix(ui): guard ProgressWindow.destroy() against double-destroy TclError#1403

Merged
amilcarlucas merged 2 commits intoArduPilot:masterfrom
yashhzd:fix/progress-window-double-destroy
Mar 21, 2026
Merged

fix(ui): guard ProgressWindow.destroy() against double-destroy TclError#1403
amilcarlucas merged 2 commits intoArduPilot:masterfrom
yashhzd:fix/progress-window-double-destroy

Conversation

@yashhzd
Copy link
Contributor

@yashhzd yashhzd commented Mar 20, 2026

Summary

  • Adds winfo_exists() guard in ProgressWindow.destroy() to prevent TclError on double-destroy
  • Adds two tests for double-destroy safety (direct and via auto-destroy at 100%)

Problem

When a flight controller connection succeeds:

  1. update_progress_bar(100, 100) auto-destroys the Toplevel (line 132-133)
  2. Context manager __exit__ calls destroy() again
  3. FlightControllerConnectionProgress.destroy() checks if self.progress_window: — always True (Python object, not widget state)
  4. ProgressWindow.destroy()self.progress_window.destroy() on already-destroyed Toplevel → TclError

Fix

# Before (crashes)
def destroy(self) -> None:
    self.progress_window.destroy()

# After (safe)
def destroy(self) -> None:
    if self.progress_window.winfo_exists():
        self.progress_window.destroy()

Test plan

  • New test_user_can_safely_destroy_progress_window_twice verifies no crash on double-destroy
  • New test_user_sees_no_crash_when_progress_completes_then_destroys verifies the exact auto-destroy + manual destroy scenario
  • CI passes

Fixes #1401

When progress reaches 100%, update_progress_bar() auto-destroys the
Toplevel. The context manager __exit__ then calls destroy() again,
raising TclError on the already-destroyed widget.

Added winfo_exists() check before calling destroy().

Fixes ArduPilot#1401

Signed-off-by: Yash Goel <yashgoel249@gmail.com>
@yashhzd yashhzd requested a review from amilcarlucas as a code owner March 20, 2026 22:10
Copilot AI review requested due to automatic review settings March 20, 2026 22:10
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Guards ProgressWindow.destroy() against double-destroy scenarios in Tkinter to prevent TclError, and adds regression tests covering the crash path described in #1401.

Changes:

  • Add a winfo_exists() check before destroying the Tkinter Toplevel.
  • Add two tests to ensure calling destroy() twice (including after auto-destroy at 100%) does not raise.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
ardupilot_methodic_configurator/frontend_tkinter_progress_window.py Adds existence guard before destroying the Tk widget to avoid TclError on double-destroy.
tests/test_frontend_tkinter_progress_window.py Adds regression tests for double-destroy, including the “auto-destroy at 100% then manual destroy” scenario.

Comment on lines +139 to +140
if self.progress_window.winfo_exists():
self.progress_window.destroy()
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

winfo_exists() and destroy() can still raise tkinter.TclError if the underlying Tcl interpreter (or root) has already been destroyed (e.g., during app shutdown). To make destroy() fully idempotent and safe during teardown, consider wrapping the existence check + destroy call in a try/except TclError and treating that error as a no-op in this cleanup path.

Suggested change
if self.progress_window.winfo_exists():
self.progress_window.destroy()
"""
Safely destroy the progress window, ignoring TclError during interpreter teardown.
"""
try:
if (
hasattr(self, "progress_window")
and self.progress_window is not None
and self.progress_window.winfo_exists()
):
self.progress_window.destroy()
except tk.TclError:
# Interpreter or widget may already be destroyed; ignore in teardown.
pass

Copilot uses AI. Check for mistakes.
Comment on lines +184 to +192
"""
User can safely destroy a progress window that was already destroyed.

GIVEN: A progress window that has been destroyed (e.g. by reaching 100%)
WHEN: destroy() is called again (e.g. by context manager __exit__)
THEN: No TclError is raised
"""
# First destroy
progress_window.destroy()
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring says the window was destroyed “e.g. by reaching 100%”, but the test destroys it manually. To keep the test self-descriptive, either adjust the GIVEN wording to “has already been destroyed” (without the 100% example) or change the setup to destroy via update_progress_bar(100, 100).

Copilot uses AI. Check for mistakes.
assert not progress_window.progress_window.winfo_exists()

# Second destroy should not raise
progress_window.destroy()
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test currently only checks that the second destroy() call doesn’t raise. Adding a post-condition assertion (e.g., assert not progress_window.progress_window.winfo_exists()) would make the intended end state explicit and help catch regressions where destroy() might recreate/replace the widget reference.

Suggested change
progress_window.destroy()
progress_window.destroy()
# Window should remain destroyed
assert not progress_window.progress_window.winfo_exists()

Copilot uses AI. Check for mistakes.
- Wrap winfo_exists + destroy in try/except TclError for interpreter
  teardown safety
- Fix test docstring to match actual test behavior
- Add post-condition winfo_exists() assertions after second destroy

Signed-off-by: Yash Goel <yashgoel249@gmail.com>
@amilcarlucas amilcarlucas merged commit 45534fe into ArduPilot:master Mar 21, 2026
16 of 19 checks passed
@amilcarlucas
Copy link
Collaborator

Thanks, good catch.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ProgressWindow.destroy() crashes with TclError on double-destroy

3 participants