Skip to content

Conversation

dalthviz
Copy link
Member

@dalthviz dalthviz commented Aug 13, 2025

References and relevant issues

Supersedes #6993
Related to napari/napari-plugin-manager#174
Part of napari/napari-plugin-manager#53

Description

Add functionality to allow plugins and napari itself to register and handle tasks status as a way to provide a GUI to inform users that there are tasks in progress while closing napari

Preview

  • Warning dialog
    imagen

  • General behavior:
    task_manager_close_warn

@dalthviz dalthviz self-assigned this Aug 13, 2025
Copy link

codecov bot commented Aug 13, 2025

Codecov Report

❌ Patch coverage is 97.34513% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.05%. Comparing base (2554b31) to head (e4eea2e).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
src/napari/utils/task_status.py 95.31% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8211      +/-   ##
==========================================
+ Coverage   92.98%   93.05%   +0.06%     
==========================================
  Files         703      705       +2     
  Lines       63365    63474     +109     
==========================================
+ Hits        58920    59063     +143     
+ Misses       4445     4411      -34     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions github-actions bot added the tests Something related to our tests label Aug 19, 2025
@dalthviz dalthviz changed the title [WIP] Add a tasks manager for plugins and napari processes [WIP] Add a tasks manager status for plugins actions and napari processes Aug 19, 2025
@dalthviz dalthviz marked this pull request as ready for review August 19, 2025 18:24
@dalthviz dalthviz requested a review from a team as a code owner August 19, 2025 18:24
@dalthviz dalthviz changed the title [WIP] Add a tasks manager status for plugins actions and napari processes Add a tasks manager status for plugins actions and napari processes Aug 19, 2025
_tasks: dict[uuid.UUID, TaskStatusItem]

def __init__(self) -> None:
self._tasks: dict[uuid.UUID, TaskStatusItem] = {}
Copy link
Contributor

Choose a reason for hiding this comment

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

Any concerns about concurrent access here? Would it be better to have a Queue of small dataclasses that bundle the uuid and the status?

Copy link
Member Author

Choose a reason for hiding this comment

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

For the moment not much since to add and update an item uuids are being used and I think the only point where this dict gets iterated is when calling is_busy, get_status and cancel_all (logic that currently only gets triggered when wanting to close the whole app which I think is a point where not other task can get added? 🤔). However, if using a queue to handle the addition of tasks still makes sense let me know!

Copy link
Contributor

Choose a reason for hiding this comment

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

Fair enough. I'll let others chime in in case they share these concerns, but for now a comment would suffice. Something along the lines "note we are using a dict here that may not be thread-safe; however given the XXX conditions, it should be ok as long as we only perform XXXX tasks in XXX endpoints".

Comment on lines 9 to 14
class Status(StringEnum):
PENDING = auto()
BUSY = auto()
DONE = auto()
CANCELLED = auto()
FAILED = auto()
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep, over napari-plugin-manager a sort of fallback of this enum is in place. Over the comment there, I left this comment regarding the match between different scenarios and the statuses: napari/napari-plugin-manager#174 (comment)

Copy link
Member Author

Choose a reason for hiding this comment

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

Note: This enum should be updated following the discussion at napari/napari-plugin-manager#174

Copy link
Contributor

@TimMonko TimMonko left a comment

Choose a reason for hiding this comment

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

Cursory review of the code looks good to me.
Functionality I definitely approve of.
This requires the napari-plugin-manager PR, correct? If so, we need to make a plugin-manager release and update pins after this is merged.
Also, is this task status something we can expose to public API / document for this reason? That way someone could have a plugin tell napari that its busy doing some process, and for this dialog to show (especially important if they use multi-threading for their plugin) on close. I think that's what you intend here, so I'd love to see a follow-up in docs, and maybe a suggested blurb that we can add to release notes highlights.

Comment on lines 608 to 618
task_status = task_status_manager.get_status()
if (
task_status
and ConfirmCloseDialog(
self,
close_app=False,
extra_info='\n'.join(task_status),
display_checkbox=False,
).exec_()
!= QDialog.Accepted
) or (
Copy link
Contributor

Choose a reason for hiding this comment

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

Just confirming that this means the tasks busy dialog box will always be shown regardless of the user's preference to show the warning on a regular close

Copy link
Member Author

@dalthviz dalthviz Aug 26, 2025

Choose a reason for hiding this comment

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

Yep, as long as there are tasks with a PENDING or BUSY status the dialog asking to confirm the application close (with a description of the tasks being processed) will be shown

Copy link
Collaborator

Choose a reason for hiding this comment

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

And it means that it is shown even if action is connected to another napari window?

Copy link
Member Author

Choose a reason for hiding this comment

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

If the napari windows are in the same process I think they could end up sharing the task manager instance indeed 🤔 It is possible to have multiple napari windows launched from the same process? Do you have a code snippet I could use to check this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Note: the multiple_viewers_.py is an example of this possibility

@TimMonko TimMonko added this to the 0.6.5 milestone Aug 26, 2025
@dalthviz
Copy link
Member Author

This requires the napari-plugin-manager PR, correct?

To be able to see the behavior described in the OP yes!

Also, is this task status something we can expose to public API / document for this reason?

I would say so 👍

@dalthviz dalthviz changed the title Add a tasks manager status for plugins actions and napari processes [WIP] Add a tasks manager status for plugins actions and napari processes Aug 27, 2025
Copy link
Collaborator

@Czaki Czaki left a comment

Choose a reason for hiding this comment

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

Hm. In the context of examples. I still think that we should add somehow the teardown to the examples. Either in the script or in a separate script that matches the file name.

Without this, we need to make a PR to docs too.

cancel_callback=worker.quit,
)
worker.started.connect(
lambda: update_task_status(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Partial?

In general, connecting lambda to signals is not a best practice, and we may think how to avoid it in codebase to not inspire other devs.

Comment on lines 73 to 77
monkeypatch.setattr(
ConfirmCloseDialog,
'exec_',
lambda *args: ConfirmCloseDialog.DialogCode.Accepted,
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I prefer to patch get_status as it will close the path triggered by the update from this PR and not hide other problems.

dalthviz and others added 2 commits September 10, 2025 11:05
…pecific examples instead. Add partial usage when connecting with worker signals
@dalthviz
Copy link
Member Author

I think this is ready for another review @Czaki , let me know if there is something else to do here!

@brisvag
Copy link
Contributor

brisvag commented Sep 23, 2025

@Czaki do you think this is close enough to go in by the end of the week? Otherwise, we'll have to postpone to the next release.

Comment on lines 174 to 179
# prevent showing `ConfirmCloseDialog` even when there are task being done
# needed to prevent napari CI (tests and docs building) from getting stuck
def no_status():
return []

task_status_manager.get_status = no_status
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't like this. It shows patching of internals in a bad way...

Copy link
Member Author

Choose a reason for hiding this comment

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

Another approach that occurs to me is to add some sort of kwarg to the thread_worker decorator to prevent registration of the task and use it for the examples 🤔 Does that make sense to you? If you have any other idea let me know!

Copy link
Collaborator

Choose a reason for hiding this comment

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

My idea.

  1. In a separate PR, modify the Viewer.close and Viewer.close_all methods to accept the force argument. It will disable all questions about closing.
  2. Modify docs conf to use Viewer.close_all(force=True)
    https://github.com/napari/docs/blob/00aefd3a7b9134f72e296a5d15f1cbbcf0e7b473/docs/conf.py#L368
  3. Come back to this PR and reimplement the check to don't ask questions on force quit.

If you implement 1. I will review it and merge fast.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would also support this change to viewer.close and close_all

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the idea @Czaki ! However, while implementing it, I noticed that it is not necessary to do changes to the viewer.close and viewer.close_all methods! Seems like those methods already have the capability to close things without showing the confirm dialog even when the option to show it is enabled. What was missing here, and I just noticed while checking the validation logic to add the force flag 😅, is to take into account the event.spontaneous() value. If that value is used also over the task status check no dialog will be shown. Last commit takes into account that and seems like it makes possible for the docs and the tests to run without showing the confirm dialog (no need to patch anything over the tests either) 🎉

Anyhow, let me know if that is good enough or if we should actually go with the force flag idea (and probably recheck the usage of the event.spontaneous() value as well as give a general check to the window close logic)

item.cancel()


task_status_manager = TaskStatusManager()
Copy link
Collaborator

Choose a reason for hiding this comment

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

I do not have a feeling like introducing this as a part of the public API with a global variable does not feel like a proper solution.

Copy link
Member Author

Choose a reason for hiding this comment

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

So if I'm understanding correctly, having this as module constant doesn't feel appropiate, right? Maybe is better to instanciate a task manager per window? That makes sense to me (and more if it is possible to launch multiple napari windows at the same time from the same process) but let me know what you think!

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes. It is possible to launch multiple napari windows in one process. And when providing such a public API, it needs to be ready for a multi-window environment.

We may introduce some private API for the meantime if we want to iterate over to provide a solution.

There is no need to provide a public API for such a feature at the beginning. And even possible future API may be functional.

Comment on lines 608 to 618
task_status = task_status_manager.get_status()
if (
task_status
and ConfirmCloseDialog(
self,
close_app=False,
extra_info='\n'.join(task_status),
display_checkbox=False,
).exec_()
!= QDialog.Accepted
) or (
Copy link
Collaborator

Choose a reason for hiding this comment

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

And it means that it is shown even if action is connected to another napari window?

@brisvag
Copy link
Contributor

brisvag commented Sep 25, 2025

Is there already a docs PR to update the docs with this feature?

@dalthviz
Copy link
Member Author

dalthviz commented Sep 25, 2025

While working on the force flag approach I noticed that no other change besides the latests ones in this PR are need so I did not create a docs PR or any other PR in the napari side of things (full explanation: #8211 (comment)). Basically, with the latest changes here, the confirm dialog doesn't show when running the test or the examples (including when those are run when building the docs) :)

Edit: Or maybe you meant like adding some docs explaining that now the napari window has methods to register task status (_register_task_status and _update_task_status)? 🤔 In the current state of this PR technically no public API is available but let me know if making those methods public (and then adding some info about them over the docs) could be worthy!

@Czaki
Copy link
Collaborator

Czaki commented Sep 25, 2025

Edit: Or maybe you meant like adding some docs explaining that now the napari window has methods to register task status (_register_task_status and _update_task_status)? 🤔

At least documentation that will explain that currently we register tasks executed by thread_worker. To explain to users what happened.

@brisvag
Copy link
Contributor

brisvag commented Sep 25, 2025

What @Czaki said, but also I forgot we ultimately made the api private, so all good on that 💪 So we're good to approve here @Czaki?

@Czaki
Copy link
Collaborator

Czaki commented Sep 25, 2025

We may think about adding a public API. But I want this API to enforce providing to which viewer the thread is connected. Even if we do not use this information, we would like to use this in the future.

@dalthviz
Copy link
Member Author

Here a PR over the docs repo adding a note about the workers task registration and the possibility of a confirm close dialog appearing if a worker is still running when closing the GUI via the window close button: napari/docs#851

@brisvag brisvag added the ready to merge Last chance for comments! Will be merged in ~24h label Sep 26, 2025
@brisvag brisvag merged commit 36b84d0 into napari:main Sep 26, 2025
37 of 38 checks passed
@github-actions github-actions bot removed the ready to merge Last chance for comments! Will be merged in ~24h label Sep 26, 2025
brisvag added a commit to napari/docs that referenced this pull request Sep 26, 2025
…ialog when closing napari GUI via close button (#851)

# References and relevant issues
<!-- What relevant resources were used in the creation of this PR?
If this PR addresses an existing issue on the repo,
please link to that issue here as "Closes #(issue-number)".
If this PR adds docs for a napari PR please add a "Depends on <napari PR
link>" -->

Depends on napari/napari#8211

# Description
<!-- What does this pull request (PR) do? Does it add new content,
improve/fix existing
context, improve/fix workflow/documentation build/deployment or
something else?
<!-- If relevant, please include a screenshot or a screen capture in
your content
change: "An image is worth a thousand words!" -->
<!-- You can use https://www.cockos.com/licecap/ or similar to create
animations. -->
<!-- You can also see a preview of the documentation changes you are
submitting by
clicking on "Details" to the right of the "Check the rendered docs
here!" check on your PR.-->

Adds a note over the threading page (`Graceful exit` section) mentioning
that starting with napari 0.6.5 workers get registered as task and a
confirm close dialog is shown if they are still running when closing
napari via the GUI close button

<!-- Previewing the Documentation Build
When you submit this PR, jobs that preview the documentation will be
kicked off.
By default, they will use the `slimfast` build (`make` target), which is
fast, because
it doesn't build any content from outside the `docs` repository and
doesn't run notebook cells.
You can trigger other builds by commenting on the PR with:

@napari-bot make <target>

where <target> can be:
html : a full build, just like the deployment to napari.org
html-noplot : a full build, but without the gallery examples from
`napari/napari`
docs : only the content from `napari/docs`, with notebook code cells
executed
slimfast : the default, only the content from `napari/docs`, without
code cell execution
slimgallery : `slimfast`, but with the gallery examples from
`napari/napari` built
-->

<!-- Final Checklist
- If images included: I have added [alt
text](https://webaim.org/techniques/alttext/)
If workflow, documentation build or deployment change:
- My PR is the minimum possible work for the desired functionality
- I have commented my code, to let others know what it does
-->

Co-authored-by: Lorenzo Gaifas <[email protected]>
@imagesc-bot
Copy link

This pull request has been mentioned on Image.sc Forum. There might be relevant details there:

https://forum.image.sc/t/napari-v0-6-5-is-out/116715/1

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

Labels

enhancement highlight PR that should be mentioned in next release notes tests Something related to our tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants