You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 62ea250
Browse filesBrowse the repository at this point in the historyBrowse files
Add safe recovery choices after resolution cancellation (#1237)
* docs: design cancellable resolution changes
Reuse analysis: extend the existing resolution request, stage movement, Stop Stage, model event queue, and PopUp lifecycles. Do not add a parallel stage executor, stop path, or public cancellation wrapper.
* docs: plan issue 486 resolution cancellation
Reuse analysis: extend the existing resolution, stage movement, Stop Stage, model event queue, and PopUp lifecycles. Reject controller-only cancellation and a parallel emergency stage executor.
* fix: dispatch stage stops without GUI delay
* fix: make resolution stage moves cancellable
Reuse analysis:
- Extended Microscope.move_stage, move_stage_offset, and stop_stage.
- A second movement executor would duplicate stage mapping, limit enforcement, and device ownership.
- Updated Numpydoc and focused movement tests.
* fix: cancel resolution changes before stopping stages
Reuse analysis:
- Kept Model.change_resolution and Model.stop_stage as canonical entry points.
- Reused the model event queue and existing stage movement methods.
- Added only private task state; no parallel public executor or stop API.
* fix: terminate cancelled resolution features safely
Reuse analysis:
- ChangeResolution now participates in the model-owned resolution lifecycle.
- No feature-specific stop path or duplicate acquisition cleanup API was added.
- Focused tests verify acquisition preparation does not resume after cancellation.
* fix: preserve stop delivery across failure modes
Reuse analysis:
- Refined the existing Controller.stop_stage retry to recognize only ObjectInSubprocess contention.
- Kept hardware failures on the normal error path.
- Extended Model.stop_stage best-effort handling so one microscope or readback failure cannot skip other stop attempts.
* docs: record issue 486 core verification
* docs: record issue 486 core publication
* test: cover both Stop Stage button bindings
Reuse analysis: exercises the existing StageController stop handler and Controller acquisition-bar binding with test-only headless button doubles. No public API or production lifecycle is added.
* feat: add cancellable resolution position recovery
Reuse analysis:
- Return movement uses Model.move_stage and the existing Stop Stage lifecycle.
- Existing stage verification enforces the saved coordinates and configured limits.
- No recovery-specific stage executor, limit validator, or stop API was added.
* feat: offer safe resolution-change recovery choices
Reuse analysis: the dialog uses Navigate's existing PopUp, event queue, model thread pool, stage-control disablement, and Stop Stage lifecycle. It introduces no parallel movement or stop executor.
* fix: restore controls after rejected resolution return
Reuse analysis: rejected and completed returns publish the existing resolution_return_complete event. Validation continues through each stage's existing strict verify_abs_position contract; no alternate limit or UI recovery path was added.
* docs: record issue 486 recovery publication
* fix: wait for stopped-position readback before recovery
Reuse analysis: extends the existing private resolution task and Stop Stage lifecycle with one completion event. It preserves the canonical stop, readback, validation, event, and return paths without adding another stage executor or public API.
* Fix resolution recovery and acquisition stop regressions
---------
Co-authored-by: Annie Wang <annie.xd.wang@gmail.com>
Run the two named tests. Expected: the recovery command and snapshot store are absent.
548
548
549
-
-[]**Step 3: Add recovery ownership and validation**
549
+
-[x]**Step 3: Add recovery ownership and validation**
550
550
551
551
On terminal cancellation, retain an immutable copy of the pre-movement coordinates in a separate private recovery record after clearing the active task. Mark Return eligible only when every requested axis passes the affected stage's existing strict absolute-position validation. A newer resolution task invalidates an older recovery record.
552
552
@@ -559,7 +559,7 @@ class _ResolutionRecovery:
559
559
return_allowed: bool
560
560
```
561
561
562
-
-[]**Step 4: Implement keep and return commands**
562
+
-[x]**Step 4: Implement keep and return commands**
563
563
564
564
Keep atomically clears the matching snapshot and emits no movement. Return validates task ID and eligibility, creates a `returning` task before starting its worker, clears the popup snapshot exactly once, calls the existing cancellation-aware `Model.move_stage(..., wait_until_done=True)`, updates actual positions, and emits `resolution_return_complete` with success or cancellation.
Patch the popup class with a complete fake exposing `showup`/`dismiss`. Assert that two events with the same task ID create one popup; Keep dispatches `("resolution_recovery", task_id, "keep")`; Return dispatches `"return"`; and `resolution_return_complete` clears the tracked popup/task state.
628
628
629
-
-[]**Step 3: Run popup/controller tests and verify RED**
629
+
-[x]**Step 3: Run popup/controller tests and verify RED**
630
630
631
631
Run only the new popup tests and named controller tests. Expected: imports and event handlers are absent.
632
632
633
-
-[]**Step 4: Implement the view**
633
+
-[x]**Step 4: Implement the view**
634
634
635
635
Build a `PopUp` titled `Resolution Change Cancelled` with the approved explanatory text and buttons `Keep Current Position` and `Return to Previous Position`. Focus Keep, bind Escape and `WM_DELETE_WINDOW` to `_keep`, disable Return when `return_enabled` is false, and dismiss before either callback. Do not start model work from the view itself.
Register the two event handlers through the existing event-listener dictionary. Track the displayed task ID to deduplicate and dispatch choices through the existing model thread pool. Before Return, call `stage_controller.view.toggle_button_states(True, stage_controller.stage_axes)` after the modal closes; the separate Stop Stage frame remains enabled. On `resolution_return_complete`, call `stage_controller.force_enable_all_axes()` and clear the tracked task ID.
Confirm that the recovery PR contains no duplicate stage executor, stop path, or acquisition restart.
712
712
713
-
-[]**Step 4: Push and open the recovery PR**
713
+
-[x]**Step 4: Push and open the recovery PR**
714
714
715
715
Push `kdean/issue-486-resolution-recovery` and open a draft PR based on `kdean/issue-486-stop-resolution-change`. Link the design, plan, and core PR; list test evidence and Xvfb limitation; request the designated reviewer if known; and reference #486 without a second closing directive.
716
716
@@ -767,3 +767,48 @@ Commit only the new test and the design/plan updates, then push the commit as a
767
767
fast-forward update to `kdean/issue-486-stop-resolution-change`. Confirm the
768
768
live PR head and report fresh CI status without treating absent or pending jobs
769
769
as success.
770
+
771
+
### Task 10: Wait for Stop Stage readback before offering Return
- Consumes: `_ResolutionChangeTask.cancel_event`, `Model.stop_stage`, and `Model._finish_resolution_change`.
782
+
- Produces: task-owned stop-completion synchronization that preserves immediate cancellation but defers recovery validation and notification until stopped-position readback completes.
783
+
784
+
-[x]**Step 1: Reproduce the hardware ordering failure**
785
+
786
+
Block the real `Model.stop_stage` path inside a microscope double while a second
787
+
thread finalizes the cancelled resolution task. Assert that no cancellation
788
+
event is emitted until the stop is released, then require the emitted payload to
789
+
contain the stopped position and enable Return.
790
+
791
+
-[x]**Step 2: Verify RED**
792
+
793
+
Run the named concurrency test against the current PR #1237 implementation.
794
+
Expected: failure because `resolution_change_cancelled` is emitted while the
0 commit comments