From 655a6f65ca64ea699db32c62e0105b80aa0247d6 Mon Sep 17 00:00:00 2001 From: Peter Story Date: Thu, 11 Apr 2024 09:09:27 -0400 Subject: [PATCH] Terminate subprocesses when the application is quit For #3 --- src/pydiode/gui/common.py | 25 ++++++++++++++----------- src/pydiode/gui/main.py | 16 ++++++++++++++-- src/pydiode/gui/receive.py | 8 +++++--- src/pydiode/gui/send.py | 10 ++++------ 4 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/pydiode/gui/common.py b/src/pydiode/gui/common.py index 29e1b7a..7163cc2 100644 --- a/src/pydiode/gui/common.py +++ b/src/pydiode/gui/common.py @@ -77,30 +77,30 @@ def stuck_running(returncodes): return False -def check_subprocesses(widget, cancelled, *args): +def check_subprocesses(widget, cancelled, processes): """ Check whether all the subprocesses have exited. If so, display their error messages and clean up after them. :param widget: Used to schedule another check :param cancelled: Boolean variable indicating cancellation request - :param args: An array of tuples, each containing a subprocess's name and - its popen object. + :param processes: An array of tuples, each containing a subprocess's name + and its popen object. """ # If requested, cancel subprocesses if cancelled.get(): # Signal each process to exit - for name, popen in args: + for name, popen in processes: popen.terminate() # Mark this cancellation request as handled cancelled.set(False) # At the next check, hopefully the processes will have exited widget.after( - SLEEP, lambda: check_subprocesses(widget, cancelled, *args) + SLEEP, lambda: check_subprocesses(widget, cancelled, processes) ) else: # Get returncodes for exited processes, None for running processes - returncodes = [popen.poll() for name, popen in args] + returncodes = [popen.poll() for name, popen in processes] # Are any of the subprocesses still running? still_running = any(code is None for code in returncodes) @@ -109,10 +109,10 @@ def check_subprocesses(widget, cancelled, *args): # Request termination of the processes cancelled.set(True) widget.after( - SLEEP, lambda: check_subprocesses(widget, cancelled, *args) + SLEEP, lambda: check_subprocesses(widget, cancelled, processes) ) # Describe the issue - process_names = [name for name, popen in args] + process_names = [name for name, popen in processes] error_msgs = get_premature_errors( list(zip(process_names, returncodes)) ) @@ -121,15 +121,18 @@ def check_subprocesses(widget, cancelled, *args): # If subprocesses are still running, keep waiting for them elif still_running: widget.after( - SLEEP, lambda: check_subprocesses(widget, cancelled, *args) + SLEEP, lambda: check_subprocesses(widget, cancelled, processes) ) # Otherwise, all subprocesses have exited else: # If any subprocesses exited irregularly, describe the issue - error_msgs = get_process_errors(args) + error_msgs = get_process_errors(processes) if error_msgs: showerror(title="Error", message=error_msgs) # Clean up - for name, popen in args: + for name, popen in processes: popen.stdout.close() popen.stderr.close() + # The array of subprocesses should be cleared, so it doesn't grow + # each time more subprocesses are started + processes.clear() diff --git a/src/pydiode/gui/main.py b/src/pydiode/gui/main.py index e6445fb..d3a12ba 100644 --- a/src/pydiode/gui/main.py +++ b/src/pydiode/gui/main.py @@ -3,13 +3,17 @@ import sys from tkinter import BooleanVar, IntVar, Listbox, StringVar, Tk, ttk - -from pydiode.gui.receive import receive_files, set_target_directory +from pydiode.gui.receive import ( + receive_files, + set_target_directory, + RECEIVE_PROCESSES, +) from pydiode.gui.send import ( add_source_files, remove_source_files, send_files, update_tx_btn, + SEND_PROCESSES, ) import pydiode.pydiode import pydiode.tar @@ -166,9 +170,17 @@ def gui_main(): port.set(config["pydiode"].get("port", "1234")) # TODO Add options for maximum bitrate and redundancy + # Override the default behavior of the Quit menu, so it doesn't cause the + # application to exit immediately + root.createcommand("tk::mac::Quit", root.quit) + # Start handling user input root.mainloop() + # Cancel send and receive subprocesses + for name, popen in SEND_PROCESSES + RECEIVE_PROCESSES: + popen.terminate() + # Save settings config["pydiode"] = { "tab": active_tab.get(), diff --git a/src/pydiode/gui/receive.py b/src/pydiode/gui/receive.py index b9be56d..09720e2 100644 --- a/src/pydiode/gui/receive.py +++ b/src/pydiode/gui/receive.py @@ -4,6 +4,9 @@ from pydiode.gui.common import check_subprocesses, SLEEP +# An array of tuples, each containing a subprocess's name and its popen object +RECEIVE_PROCESSES = [] + def set_target_directory(target): """ @@ -45,11 +48,10 @@ def receive_files( stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) + RECEIVE_PROCESSES.extend([("pydiode", pydiode), ("tar", tar)]) root.after( SLEEP, - lambda: check_subprocesses( - root, cancelled, ("pydiode", pydiode), ("tar", tar) - ), + lambda: check_subprocesses(root, cancelled, RECEIVE_PROCESSES), ) def animate(): diff --git a/src/pydiode/gui/send.py b/src/pydiode/gui/send.py index 21a9703..1ffe47c 100644 --- a/src/pydiode/gui/send.py +++ b/src/pydiode/gui/send.py @@ -16,6 +16,8 @@ OVERHEAD = 1.085 # Increment progress bars every 25 milliseconds, for smooth animation. INCREMENT_INTERVAL = 25 +# An array of tuples, each containing a subprocess's name and its popen object +SEND_PROCESSES = [] def add_source_files(sources_var, sources_list): @@ -132,14 +134,10 @@ def send_files( stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) + SEND_PROCESSES.extend([("tar", tar), ("pydiode", pydiode)]) root.after( SLEEP, - lambda: check_subprocesses( - root, - cancelled, - ("tar", tar), - ("pydiode", pydiode), - ), + lambda: check_subprocesses(root, cancelled, SEND_PROCESSES), ) increment_size = get_increment_size(sources_list, progress_bar)