chriskiehl / Gooey

Turn (almost) any Python command line program into a full GUI application with one line
MIT License
20.66k stars 1.02k forks source link

[Feature request] Raise exception on Stop Task #729

Closed fijam closed 3 years ago

fijam commented 3 years ago

I'm not sure if this is within the scope of the project, but would it be possible to raise an exception when the Stop button is clicked (much like you would have a KeyboardInterrupt when a user interrupts the program with Ctrl-C) so you can clean up?

chriskiehl commented 3 years ago

This is a dupe of https://github.com/chriskiehl/Gooey/issues/232. Closing in favor of that issue.

Good news is that I'm actually working on this feature right now ^_^ It's a bit trickier than expected thanks to Windows being Windows, but it should be ready to go soon.

You'll be able to pass in an arbitrary signal to control what happens when you click the stop button. For instance,

@Gooey(terminate_using=signal.CTRL_C_EVENT)

Which could be caught in your app as a KeyboardInterrupt.

fijam commented 3 years ago

That's great news. Thanks!

chriskiehl commented 3 years ago

Hey @fijam , I've added the ability to customize the stop strategy to the release branch. This'll let you catch when Gooey is doing a force stop. Wanna give it try and let me know if you hit any issues? Getting the signals sorted out on Windows took a fair bit of fiddling, so I'd love to hear if you run into any problems.

Branch: https://github.com/chriskiehl/Gooey/tree/1.0.9-release

You can checkout the docs here: https://github.com/chriskiehl/Gooey/blob/1.0.9-release/docs/Gracefully-Stopping.md

tl;dr: you can now pass a signal to the gooey decorator.

import signal 
@Gooey(shutdown_signal=signal.CTRL_C_EVENT)
def main(): 
   ...
fijam commented 3 years ago

Hi, I tested it quickly and while it seems to work in the sense that except clause is executed (yay!), pressing Yes on the Stop Task modal immediately closes the Gooey window and the except clause continues to run in the background. Is this the intended behavior or a problem between keyboard and chair? It would be nice to communicate to the user that the program is cleaning up (it can take a few minutes for me). Can the window be kept up until the process actually exits?

My environment is Python (3.8.10) on Windows 10 (19043.1110)

chriskiehl commented 3 years ago

Thanks for testing!

Arg.. these are the Windows quirks I mentioned which is making this tricky... Signals get sent to the whole process group, which I thought I was able to ignore in Gooey land, but I guess it's unreliable. So, it's definitely unexpected that Gooey is itself is terminating and not the desired behavior.

Are you seeing the problem when you send the CTRL_C_EVENT or for all events (CTRL_C_EVENT, CTRL_BREAK_EVENT, SIGTERM)?

One option is to change how Gooey spawns the process such that a new ProcessGroup is made. However, what sucks is that, for whatever reason, you can only send CTRL_BREAK signals under that setup, which cannot be caught in the child process via the nicer KeyboardInterrupt exception and instead require a signal handler to be added.

fijam commented 3 years ago

I tested with a handler and CTRL_BREAK_EVENT and looks like it works! Cheers.

It's too bad that you can't handle that with KeyboardInterrupt then but I guess you can't have your cake and eat it too.

fijam commented 3 years ago

I have also noticed that the exception handler does not work in an application built with PyInstaller (4.5) which may be a whole 'nother can of worms.

chriskiehl commented 3 years ago

Ok! I think I've got this a little more pinned down. All signals should work as expected, and none of them should cause the parent Gooey process to inadvertently close. It took trawling through the Python issue tracker to finally figure out that both the Python docs and Microsoft's docs are either outright incorrect, or missing very important info with regards to how signals get handled in Windows. TL;DR: I think we're in good shape (at least on Windows).

Release branch has the latest fixes.

Thanks again for testing! It's super helpful having additional feedback for fiddly OS stuff like this! ^_^

chriskiehl commented 3 years ago

@Duke-Pickle @MrSassyBritches (for visibility, since there was past interest in this issue)

fijam commented 3 years ago

Hi again, I re-tested the updated 1.0.9 release branch and now CTRL_C_EVENT / KeyboardInterrupt works as expected. Nice!

I have also been fumbling around some more with pyinstaller and discovered that interrupt handling only works if the app is built in --onedir mode with console=True (https://gist.github.com/fijam/d5ef98f365d2ba6c4a3f2984eebc5a61). Looks like a pyinstaller bug.

fijam commented 3 years ago

By the way, I see a similar behavior when packaging with cx_freeze: an interrupt works well if there is a console window alongside Gooey, but fails without console shown (base = "Win32GUI"). In the second case, clicking Stop once does nothing, and a second click kills the process without raising an exception.

Jordan-Pierce commented 1 year ago

Hi @chriskiehl I was wondering if this still holds true for a function being called from another script? I'm using the decorator

@Gooey(shutdown_signal=signal.CTRL_C_EVENT)
main():
...

When Gooey parses the arguments, those are sent to another function, and within that function if a user presses stop, I'd like to be able to cut out early, but then still save some results. When doing as @fijam using the combination of CTRL_C_EVENT / KeyboardInterrupt, when pressing stop, I get the normal error of data corruption, and nothing after the except clause (within in the function) runs. I'm guessing the except clauses needs to be on the outside of the function being called?