Skip to content
Advertisement

Timer preventing program from exiting

I’m writing a program which allows me to control a vanilla Minecraft server using python. The first thing I wanted to make is a auto-restart feature. Everything works fine except that I cannot do sys.exit() or similar things, I’m not sure but I think this is because of the Timer.

I tried t.cancel() but t is a local variable so it’s complicated to play with it.

Here’s my code:

#! /usr/bin/env python3
import os, sys, time
import subprocess
from datetime import datetime, timedelta, date
from threading import Timer

server = subprocess.Popen('./start.sh', stdin=subprocess.PIPE,shell=True)
#junk variables
content = ''
previousContent = ''

def restart():
    if datetime.today().weekday() is 3:
        server.stdin.write(bytes('stoprn', 'ascii'))
        server.stdin.flush()
        time.sleep(90)
        print('Restarting...')
        os.system('python3 start.py')
        sys.exit()
    else:
        timerStart()

def timerStart():
    today = datetime.today()
    restartDate = today.replace(day=today.day,hour=1,minute=0,second=0,microsecond=0) + timedelta(days=1)
    delta_t = restartDate-today
    secs= delta_t.total_seconds()
    t=Timer(secs, restart)
    t.start()

timerStart()

while True:
    time.sleep(0.1)
    
    #stdout
    f = open('logs/latest.log')
    content = f.read()
    if previousContent != '':
        if previousContent in content:
            content.replace(previousContent,'')
            if content != '':
                print(content)
    previousContent = f.read()
    f.close()

    #stdin
    command = input('')
    if command:
        if command == 'stop':
            server.stdin.write(bytes('stoprn', 'ascii'))
            server.stdin.flush()
            time.sleep(20)
            sys.exit()
        else:
            server.stdin.write(bytes(command + 'rn', 'ascii'))
            server.stdin.flush()

If someone could at least put me on the right track, it would really help me

Advertisement

Answer

When instantiate the threading.Timer class, and call its start() method in the corresponding object, a new non-daemon execution thread is created and started behind the scenes for execute the method “restart”. The created execution thread, waits for the time interval specified in the “secs” variable when instantiated the threading.Timer class to elapse, and then executes the specified “restart” method. The reason why the execution of the script does not end when you invoke sys.exit(), is because the main execution thread of the application, in order to finish its execution (and thus the execution of the script), has to wait for all other non-daemon threads created to finish first. That is, when you call sys.exit(), the process’s main execution thread, will have to wait for the thread created by the instantiation of the threading.Timer class, to finish its execution. To solve that, what you can do, as you mentioned, is to call t.cancel(). Now for that, you must first move the declaration of the variable “t” to the global scope, to be able to use it in the ‘timerStart’ function, and in the block of code executed to handle the “stop” command. For example, after the declaration and initialization of the variable “previousContent”, you can declare there the variable t and initialize it to None. Then, before assign to it the instance of threading.Timer, you have to use the “global” keyword that allows to modify the variable outside the current scope(outside the scope of the ‘timerStart’ function), and finally, when processing the “stop” command, call t.cancel(). In short, the final code would look like this:

# ! /usr/bin/env python3
import os, sys, time
import subprocess
from datetime import datetime, timedelta, date
from threading import Timer

server = subprocess.Popen('./start.sh', stdin=subprocess.PIPE, shell=True)
# junk variables
content = ''
previousContent = ''
t = None


def restart():
    if datetime.today().weekday() is 3:
        server.stdin.write(bytes('stoprn', 'ascii'))
        server.stdin.flush()
        time.sleep(90)
        print('Restarting...')
        os.system('python3 start.py')
        sys.exit()
    else:
        timerStart()


def timerStart():
    today = datetime.today()
    restartDate = today.replace(day=today.day, hour=1, minute=0, second=0, microsecond=0) + timedelta(days=1)
    delta_t = restartDate - today
    secs = delta_t.total_seconds()
    global t
    t = Timer(secs, restart)
    t.start()


timerStart()

while True:
    time.sleep(0.1)

    # stdout
    with open('logs/latest.log') as f:
        # Is better to open the file using a "with", such that the file is guaranteed to be closed,
        # even in the case of an exception
        content = f.read()
        if previousContent != '':
            if previousContent in content:
                content.replace(previousContent, '')
                if content != '':
                    print(content)
        previousContent = f.read()

    # stdin
    command = input('')
    if command:
        if command == 'stop':
            # Abort the threading.Timer's non-daemon execution thread:
            t.cancel()
            # Blocks the calling thread until the thread whose join() method is called is terminated:
            t.join()
            server.stdin.write(bytes('stoprn', 'ascii'))
            server.stdin.flush()
            time.sleep(20)
            sys.exit()
        else:
            server.stdin.write(bytes(command + 'rn', 'ascii'))
            server.stdin.flush()

Also, is good to know, that the thread created by instantiating the threading.Timer class, could be canceled, only before it start to execute the “restart” method. The class threading.Timer, inherits from threading.Thread, that is way it creates and fires the execution of a new non-daemon execution thread, when start() is called.

References:

Timer Objects Thread Objects

User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement