103

I'm busy writing a small game server to try out flask. The game exposes an API via REST to users. It's easy for users to perform actions and query data, however I'd like to service the "game world" outside the app.run() loop to update game entities, etc. Given that Flask is so cleanly implemented, I'd like to see if there's a Flask way to do this.

Neuron
  • 5,141
  • 5
  • 38
  • 59
Marinus
  • 2,210
  • 2
  • 15
  • 15
  • You mean something like Flask-Admin? Or if you are using an ORM (SQL-Alchemy), then you can just create a new db session to query the database even if the application is running. – reptilicus Jan 17 '13 at 18:38
  • If you actually need to do a lot of computation, you might want to use the subprocess module, and simply spawn new processes to do that additional computation. – Maus Jan 17 '13 at 22:02
  • That is a plan, however the sub process will be manipulating data structures, that you'd like to access and set via the exposed flask api. Will I not run into problems? – Marinus Jan 18 '13 at 14:41
  • this looks pretty good http://flask.pocoo.org/docs/patterns/celery/ – cecilphillip Jun 14 '14 at 22:39
  • possible duplicate of [Background Worker with Flask](http://stackoverflow.com/questions/11256002/background-worker-with-flask) – George Hilliard Aug 13 '14 at 19:40
  • It looks like there's [a hackish way to do it](https://stackoverflow.com/a/9932189/240418), but I don't think this is technically supported. I also found [this answer](https://stackoverflow.com/a/11257228/240418), which talks about using flask-celery for this. – girasquid Jan 17 '13 at 18:42
  • @girasquid Agreed, celery or some other task queue system is ideal for this sort of thing - you generally have less control over threads or sub-processes (since the parent process may be reaped by the server without notice). – Sean Vieira Jan 18 '13 at 03:14

3 Answers3

104

Your additional threads must be initiated from the same app that is called by the WSGI server.

The example below creates a background timer-thread that executes every 5 seconds and manipulates data structures that are also available to Flask routed functions.

import threading
import atexit
from flask import Flask

POOL_TIME = 5 #Seconds
    
# variables that are accessible from anywhere
common_data_struct = {}
# lock to control access to variable
data_lock = threading.Lock()
# timer handler
your_timer = threading.Timer(0,lambda x: None,())    
def create_app():
    app = Flask(__name__)

    def interrupt():
        global your_timer
        your_timer.cancel()

    def do_stuff():
        global common_data_struct
        global your_timer
        with data_lock:
            pass
            # Do your stuff with common_data_struct Here

        # Set the next timeout to happen
        your_timer = threading.Timer(POOL_TIME, do_stuff, ())
        your_timer.start()   

    def do_stuff_start():
        # Do initialisation stuff here
        global your_timer
        # Create your timer
        your_timer = threading.Timer(POOL_TIME, do_stuff, ())
        your_timer.start()

    # Initiate
    do_stuff_start()
    # When you kill Flask (SIGTERM), cancels the timer
    atexit.register(interrupt)
    return app

app = create_app()          

Call it from Gunicorn with something like this:

gunicorn -b 0.0.0.0:5000 --log-config log.conf --pid=app.pid myfile:app

Signal termination works best on OS's other than Windows. Although this creates a new timer after each timeout, the other timers should eventually be garbage-collected.

Josiah Yoder
  • 3,321
  • 4
  • 40
  • 58
caio
  • 1,204
  • 1
  • 10
  • 6
  • 14
    I found this to be problematic when using flask's auto-reload functionality (a new thread got created on every reload). To fix this, I used [werkzeug.serving.is_running_from_reloader](http://werkzeug.pocoo.org/docs/0.10/serving/#werkzeug.serving.is_running_from_reloader) to only create it when the app is not running from the reloader. – raffomania Sep 06 '15 at 13:05
  • This is a nice solution; helps deal with flask apps that use multiprocessing or threading modules. I like it. – Shan Valleru Jan 14 '17 at 07:08
  • 3
    This example is a little confusing because the object created called "yourThread" is not a thread. It's a timer: suggest you rename it. And, when yourTimer is executed ( in doStuff ), I don't know if yourThread is valid - ie, if you can execute cancel on a Timer that hasn't been executed. It has the efficiency issue that it's creating a new object every execution, if that might be an issue. – Brian Bulkowski Apr 10 '17 at 04:07
  • 1
    The correct statement for checking "is_running_in_background()" is like so: from werkzeug.serving import is_running_from_reloader if is_running_from_reloader() == False: startBackground() – Brian Bulkowski Apr 10 '17 at 16:25
  • Starting the thread from the non reloader process will prevent the thread from accessing your Flask app context (it runs in the reloader process), so depending on what you want to achieve, you have to decide between starting it from the main process (`is_running_from_reloader()` returning `False`) or the reloader process (`is_running_from_reloader()` returning `True`). BTW, on reload, the previous reloader process is killed (with all its threads) and a new one is spawned. – Tey' Aug 14 '20 at 22:41
  • Love this solution, this is exactly what I was looking for. But why create a new thread each time? I tried creating just one thread, and doing a sleep inside an infinite loop in doStuff(), and so far it seems to work fine. Are the two solutions equivalent? Is it better (for what reason?) to re-create a new thread on each timer ? – joao Jan 28 '23 at 07:30
  • On Windows, catching the termination signal from Python is tricky. I've only gotten it to work [with Cygwin, and even that may not work any more](https://stackoverflow.com/a/40644694/1048186) – Josiah Yoder Jan 28 '23 at 16:07
  • On Windows, if a route calls `with data_lock: global yourThread; yourThread.cancel()` first, then the signal is captured. But as long as the timer is running, the signal is not captured. So I can shutdown my server cleanly by first visiting my `shutdown` route, then by Ctrl-C'ing the server. (In PyCharm, I hit the red square to trigger a "Ctrl-C") – Josiah Yoder Jan 28 '23 at 16:24
  • This example does a good job showing how to communicate between threads using a locking variable. I found it extended simply to launching a thread, sharing a variable to tell that thread's inner loop to cancel, and joining on that thread through a "stop" route to wait for the thread to wrap up. Although it is unlikely this will scale to a production server, it is a very nice example for making a small single-user or few-user app. – Josiah Yoder Jan 28 '23 at 20:44
10

In addition to using pure threads or the Celery queue (note that flask-celery is no longer required), you could also have a look at flask-apscheduler:

https://github.com/viniciuschiele/flask-apscheduler

A simple example copied from https://github.com/viniciuschiele/flask-apscheduler/blob/master/examples/jobs.py:

from flask import Flask
from flask_apscheduler import APScheduler


class Config(object):
    JOBS = [
        {
            'id': 'job1',
            'func': 'jobs:job1',
            'args': (1, 2),
            'trigger': 'interval',
            'seconds': 10
        }
    ]

    SCHEDULER_API_ENABLED = True


def job1(a, b):
    print(str(a) + ' ' + str(b))

if __name__ == '__main__':
    app = Flask(__name__)
    app.config.from_object(Config())

    scheduler = APScheduler()
    # it is also possible to enable the API directly
    # scheduler.api_enabled = True
    scheduler.init_app(app)
    scheduler.start()

    app.run()
Andreas Bergström
  • 13,891
  • 5
  • 59
  • 53
2

First, you should use any WebSocket or polling mechanics to notify the frontend part about changes that happened. I use Flask-SocketIO wrapper, and very happy with async messaging for my tiny apps.

Nest, you can do all logic which you need in a separate thread(s), and notify the frontend via SocketIO object (Flask holds continuous open connection with every frontend client).

As an example, I just implemented page reload on backend file modifications:

<!doctype html>
<script>
    sio = io()

    sio.on('reload',(info)=>{
        console.log(['sio','reload',info])
        document.location.reload()
    })
</script>
class App(Web, Module):

    def __init__(self, V):
        ## flask module instance
        self.flask = flask
        ## wrapped application instance
        self.app = flask.Flask(self.value)
        self.app.config['SECRET_KEY'] = config.SECRET_KEY
        ## `flask-socketio`
        self.sio = SocketIO(self.app)
        self.watchfiles()

    ## inotify reload files after change via `sio(reload)``
    def watchfiles(self):
        from watchdog.observers import Observer
        from watchdog.events import FileSystemEventHandler
        class Handler(FileSystemEventHandler):
            def __init__(self,sio):
                super().__init__()
                self.sio = sio
            def on_modified(self, event):
                print([self.on_modified,self,event])
                self.sio.emit('reload',[event.src_path,event.event_type,event.is_directory])
        self.observer = Observer()
        self.observer.schedule(Handler(self.sio),path='static',recursive=True)
        self.observer.schedule(Handler(self.sio),path='templates',recursive=True)
        self.observer.start()


Dmitry Ponyatov
  • 348
  • 1
  • 11