Home > Back-end >  How could I stop the script without having to wait for the time set for interval to pass?
How could I stop the script without having to wait for the time set for interval to pass?

Time:06-01

In this script I was looking to launch a given program and monitor it as long as the program exists. Thus, I reached the point where I got to use the threading's module Timer method for controlling a loop that writes to a file and prints out to the console a specific stat of the launched process (for this case, mspaint).

The problem arises when I'm hitting CTRL C in the console or when I close mspaint, with the script capturing any of the 2 events only after the time defined for the interval has completely ran out. These events make the script stop.

For example, if a 20 seconds time is set for the interval, once the script has started, if at second 5 I either hit CTRL C or close mspaint, the script will stop only after the remaining 15 seconds will have passed.

I would like for the script to stop right away when I either hit CTRL C or close mspaint (or any other process launched through this script).

The script can be used with the following command, according to the example: python.exe mon_tool.py -p "C:\Windows\System32\mspaint.exe" -i 20

I'd really appreciate if you could come up with a working example.

I had used python 3.10.4 and psutil 5.9.0 .

This is the code:

# mon_tool.py

import psutil, sys, os, argparse
from subprocess import Popen
from threading import Timer
debug = False


def parse_args(args):   
    parser = argparse.ArgumentParser()
    parser.add_argument("-p", "--path", type=str, required=True)
    parser.add_argument("-i", "--interval", type=float, required=True)
    return parser.parse_args(args)

def exceptionHandler(exception_type, exception, traceback, debug_hook=sys.excepthook):
    '''Print user friendly error messages normally, full traceback if DEBUG on.
       Adapted from http://stackoverflow.com/questions/27674602/hide-traceback-unless-a-debug-flag-is-set
    '''
    if debug:
        print('\n*** Error:')
        debug_hook(exception_type, exception, traceback)
    else:
        print("%s: %s" % (exception_type.__name__, exception))
sys.excepthook = exceptionHandler
      
def validate(data):
    try:
        if data.interval < 0:            
            raise ValueError
    except ValueError:        
        raise ValueError(f"Time has a negative value: {data.interval}. Please use a positive value")
def main():
    args = parse_args(sys.argv[1:])
    validate(args)


    # creates the "Process monitor data" folder in the "Documents" folder
    # of the current Windows profile
    default_path: str = f"{os.path.expanduser('~')}\\Documents\Process monitor data"
    if not os.path.exists(default_path):
        os.makedirs(default_path)  

    abs_path: str = f'{default_path}\data_test.txt'

    print("data_test.txt can be found in: "   default_path)


    # launches the provided process for the path argument, and
    # it checks if the process was indeed launched
    p: Popen[bytes] = Popen(args.path)
    PID = p.pid    
    isProcess: bool = True
    while isProcess:
        for proc in psutil.process_iter():
            if(proc.pid == PID):
                isProcess = False

    process_stats = psutil.Process(PID)

    # creates the data_test.txt and it erases its content
    with open(abs_path, 'w', newline='', encoding='utf-8') as testfile:
            testfile.write("")
             

    # loop for writing the handles count to data_test.txt, and
    # for printing out the handles count to the console
    def process_monitor_loop():      
        with open(abs_path, 'a', newline='', encoding='utf-8') as testfile:
            testfile.write(f"{process_stats.num_handles()}\n")
            print(process_stats.num_handles())
        Timer(args.interval, process_monitor_loop).start() 
    process_monitor_loop()
                      

if __name__ == '__main__':
    main()

Thank you!

CodePudding user response:

You can try registering a signal handler for SIGINT, that way whenever the user presses Ctrl C you can have a custom handler to clean all of your dependencies, like the interval, and exit gracefully. See this for a simple implementation.

CodePudding user response:

I think you could use python-worker (link) for the alternatives

import time
from worker import worker, enableKeyboardInterrupt

# make sure to execute this before running the worker to enable keyboard interrupt
enableKeyboardInterrupt()


# your codes
...


def main():
    # your codes
    ...


    @worker(keyboard_interrupt=True)
    def process_monitor_loop():
        with open(abs_path, 'a', newline='', encoding='utf-8') as testfile:
            testfile.write(f"{process_stats.num_handles()}\n")
            print(process_stats.num_handles())
       time.sleep(args.interval)
       process_monitor_loop()

    
    process_monitor_loop()

if __name__ == '__main__':
    main()

here your process_monitor_loop will be able to stop even if it's not exactly 20 sec of interval

  • Related