2

While having problems with python timers I found that when bottle.py is run with "reloader = True", all timer functions run twice in rapid succession.

I have tried this with several different methods of invoking the timers and the result is the same in all instances (double firing).

The sample code:

 #!/usr/bin/env python

 from threading import Timer
 from bottle import *

# Short timer
def short_time():
        t = Timer(1, short_time)
        t.daemon = True
        t.start()
        print "Short Time..."

# Long timer
def long_time():
        t = Timer(5, long_time)
        t.daemon = True
        t.start()
        print "Long Time..."

# The App
app = Bottle()

@app.route('/status')
def default():
        return "OK"

#Run the app -----
if __name__ == '__main__':

    # Start the short timer.  
    short_time()

    # Start the long timer.  
    long_time()

    # Run the app
    # This interferes with the timers
    run(app, host='0.0.0.0', port=8002, reloader=True) 

    #This one works as expected
    #run(app, host='0.0.0.0', port=8002) #This works fine

The output with reloader enabled:

Short Time...
Short Time...
Short Time...
Short Time...
Short Time...
Short Time...
Short Time...
Short Time...
Long Time...
Short Time...
Long Time...
Short Time...
Short Time...
Short Time...

The expected output (without reloader):

Short Time...
Short Time...
Short Time...
Short Time...
Long Time...
Short Time...
Short Time...
Short Time...
Short Time...
Short Time...
Long Time...
Short Time...
Short Time...

Any ideas on how to use reloader, but prevent the timer problem?

Cœur
  • 37,241
  • 25
  • 195
  • 267
user3091850
  • 102
  • 1
  • 9

1 Answers1

3

When reloader=True is set, the bottle process re-runs the script as a child process:

if reloader and not os.environ.get('BOTTLE_CHILD'):
    import subprocess
    lockfile = None
    try:
        fd, lockfile = tempfile.mkstemp(prefix='bottle.', suffix='.lock')
        os.close(fd)  # We only need this file to exist. We never write to it
        while os.path.exists(lockfile):
            args = [sys.executable] + sys.argv
            environ = os.environ.copy()
            environ['BOTTLE_CHILD'] = 'true'
            environ['BOTTLE_LOCKFILE'] = lockfile
            p = subprocess.Popen(args, env=environ)

The child is then restarted every interval seconds (default 1).

Anything run at the top-level of the script is run both when you start the bottle server, and each time the child process is started again.

As such, both the parent and child process are running timers, independently. The long timer in the child process never gets executed as that process is killed before the 5 seconds are up, but the short timer may just manage to fire before the parent process kills the child to start it again.

You can detect if you are in the child process by testing for the BOTTLE_CHILD environment variable:

import os
if os.environ.get('BOTTLE_CHILD'):
    # in the child process, do something special perhaps?
    # this will be executed *each time the child is restarted*
else:
    # in the parent process, which restarts the child process
Community
  • 1
  • 1
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343