Python threading.Timer object not functioning when compiled to .exe
Asked Answered
T

1

7

This is a follow up to https://stackoverflow.com/questions/37684111/ironpython-exe-file-closing-immediately-no-exception-thrown

I figured out that my program is not working once compiled due to an issue with the Timer object in the threading library. I've included the library in my \Lib\site-packages directory and added the directory to the path in the program. Here is the test code I'm using - a simple counting program:

import sys
from threading import Timer

sys.path.append('C:\Users\[user]\Documents\Visual Studio 2015\Projects\Testing Timer Compilation issue\Testing Timer Compilation issue')
sys.path.append('C:\Users\[user]\Documents\Visual Studio 2015\Projects\Testing Timer Compilation issue\Testing Timer Compilation issue\Lib')

class Chron():
    def __init__(self):
        self.t = Timer(2, self.count)
        self.t.start()
        self.i = 0

    def count(self):
        print(self.i)
        self.i += 1
        if self.i <= 15:
            self.t = Timer(2, self.count)
            self.t.start()

c = Chron()

Works perfectly in the Interactive Interpreter within Visual Studio, but once I use pyc.py to compile to an exe file, it will not run, and simply closes after ~5 seconds, no exception thrown.

As mentioned in the previous question, I have a program with a Timer in it that I need compiled, as the source code contains sensitive credentials. Are there any tricks necessary to make the Timer work within an exe? Is it simply incompatible?

Edit: 6 days without an answer. Unfortunately, there doesn't seem to be any resources for this specific issue anywhere on the internet. It's almost as if I'm the only one having this issue. This seems strange to me, since the problem seems to be with the Timer object itself, and I can't imagine that no one else has tried to deploy an application with a Timer in it. Any insight would be helpful at this point, as I am completely stumped.

Tintinnabulum answered 7/6, 2016 at 17:42 Comment(5)
Have you tried leaving prints in the code to see where exactly it stops? It might be helpful for tracking the problemOrbadiah
That's how I got to where I am presently. In my previous question, which I linked above, you'll see that this question is actually the product of doing just that on a much more complex piece of code. This one breaks the second it tries to start the timer counting.Tintinnabulum
so it doesn't get into the start function? Maybe there is something in the start functionOrbadiah
That's certainly possible, but I don't know enough about threading to look through it. On top of that, it's a standard Python library, so I would think it very strange for there to be an issue of this magnitude with it. Not saying your wrong, just that it's strange.Tintinnabulum
Could it be that the EXE you have generated is not waiting for all threads to terminate? What happens if you simply sleep for 30s after c = Chron()? Do you see the expected output at that point?Plath
P
5

The problem is that you are relying on the underlying Python interpreter to gracefully handle the case where the main thread of your executable has terminated, but there should be some others still running.

Running the code directly with CPython or IronPython works as expected. The Timer objects that you create are actually a specialization of a Thread. The interpreter recognizes that there are some non-daemon Threads still active and so doesn't terminate. See the docs for an explanation of daemon threads if you don't know the difference between the 2 types of Threads.

However, when you run as an executable, it would appear that the code IronPython uses to wrap the interpreter is not so kind. It just waits for the main thread to end and then closes everything. This happens despite the fact that your Timers are declared to be non-daemon Threads. Arguably this is a bug in IronPython.

The solution is therefore to leave your main thread running while your Timer threads are still operating. The simplest way to do that for this sample code is just to sleep - for example:

import sys
sys.path.append(r"c:\Program Files (x86)\IronPython 2.7\Lib")
from threading import Timer
from time import sleep

class Chron():
    def __init__(self):
        self.t = Timer(2, self.count)
        self.t.start()
        self.i = 0

    def count(self):
        print(self.i)
        self.i += 1
        if self.i <= 15:
            self.t = Timer(2, self.count)
            self.t.start()

c = Chron()
sleep(35)

However, for more complicated applications, you should consider some communication between your threads to coordinate when to close - e.g. using join() to wait for thread termination.

Plath answered 7/7, 2016 at 17:6 Comment(4)
Since threading.Timer subclasses threading.Thread, might another solution be to do self.t.daemon = True prior to calling self.t.start()?Denominate
@StevenRumbalski Nice idea, but think you've got the logic the wrong way round... Setting it to True means the Interpreter is allowed to ignore this Thread/Timer! Looking it up, they default to False, so I don't think this would fix it...Plath
Yeah, you're right. But as far as the default goes they default to the status of the main thread. Is it possible that Iron Python sets the main thread incorrectly?Denominate
@StevenRumbalski Fair point - that would be surprising but could explain this... I've just checked and the Timer's daemon property is set to False, so that's not it.Plath

© 2022 - 2024 — McMap. All rights reserved.