MrLeeh
MrLeeh

Reputation: 5589

Background thread gets started twice with Flask-Socketio

I have created a small Flask webapp using socketio that is supposed to visualize a brew controller. The hardware is a Raspberry Pi and the controller part (hardware binding and data collection) is done in a separate background thread which is started in create_app. I need to make sure the background thread only gets started once (even if I create multiple app objects). So I use the BrewController.get_instance()function to realize some kind of singleton pattern.

import os
import time
import threading
import arrow
from sqlitedict import SqliteDict
from flask import Flask
from flask_bootstrap import Bootstrap
from flask_socketio import SocketIO
from flaskext.lesscss import lesscss

from config import config
from .brewcontroller import BrewController

background_thread = threading.Thread()

# Flask Plugins
bootstrap = Bootstrap()
socketio = SocketIO()
brew_controller = BrewController.get_instance()


db = SqliteDict('process_data.sqlite', tablename='pd', autocommit=False)
db.setdefault('t', [])
db.setdefault('temp_sp', [])
db.setdefault('temp_ct', [])
db.setdefault('ht_pwr', [])
db.commit()

from . import events  # noqa


def create_app(config_name=None):
    app = Flask(__name__)

    if config_name is None:
        config_name = os.environ.get('PIBREW_CONFIG', 'development')
    app.config.from_object(config[config_name])

    # init flask plugins
    lesscss(app)
    bootstrap.init_app(app)
    socketio.init_app(app)

    # create blueprints
    from .main import main as main_blueprint
    app.register_blueprint(main_blueprint, url_prefix='/')

    # init the brew controller and start the background task if none
    # exists yet
    print(brew_controller)
    if not brew_controller.initialized:
        brew_controller.init_app(app)

        background_thread = threading.Thread(
            target=process_controller,
            args=[app.config['PROCESS_INTERVAL']],
            daemon=True
        )
        print('controller started')
        background_thread.start()

    return app


def process_controller(interval):

    while(1):

        current_time = arrow.now()
        brew_controller.process()

        data = {
            't': current_time.format('HH:mm:ss'),
            'temp_sp': '{:.1f}'.format(brew_controller.temp_setpoint),
            'temp_ct': '{:.1f}'.format(brew_controller.temp_current),
            'ht_en': brew_controller.heater_enabled,
            'mx_en': brew_controller.mixer_enabled,
            'ht_pwr': '{:.1f}'.format(brew_controller.heater_power_pct),
            'ht_on': brew_controller.heater_on,
        }

        x = db['t']
        x.append(data['t'])
        db['t'] = x

        db['temp_sp'].append(data['temp_sp'])
        db['temp_sp'] = db['temp_sp']

        db['temp_ct'].append(data['temp_ct'])
        db['temp_ct'] = db['temp_ct']

        db['ht_pwr'].append(data['ht_pwr'])
        db['ht_pwr'] = db['ht_pwr']

        db.commit()

        socketio.emit('update', data)
        time.sleep(interval)

However the thread is still getting started twice and I even get two different BrewController instances. So I end up with twice as much data in my database and duplicated values.

The output after I call manage.py run looks like this (I printed the brewcontroller instances to see if they are different):

<pibrew.brewcontroller.BrewController object at 0x105777208>
<pibrew.brewcontroller.BrewController object at 0x105777208>
 controller started
 * Restarting with stat
<pibrew.brewcontroller.BrewController object at 0x10ca04240>
<pibrew.brewcontroller.BrewController object at 0x10ca04240>
 controller started
 * Debugger is active!
 * Debugger pin code: 121-481-821
(31213) wsgi starting up on http://0.0.0.0:5000

I discovered that I can suppress this by setting use_reloader argument in my manage.py to False.

@manager.command
def run():
    app = create_app()
    socketio.run(app, host='0.0.0.0', port=5000, use_reloader=False)

But what is the reason for this double-starting in the first place. For me it seems like there are two processes created. Can someone explain what is happening and what is the best way to prevent this.

Upvotes: 0

Views: 1810

Answers (2)

MrLeeh
MrLeeh

Reputation: 5589

Based on Miguels Answer I placed a before_first_request handler inside my create_app function that handles the brew_controller creation and starts up the background thread.

def create_app(config_name=None):
    app = Flask(__name__)

    # ...

    @app.before_first_request
    def init_brew_controller():
        # init the brew controller and start the background task if none
        # exists yet
        if not brew_controller.initialized:
            brew_controller.init_app(app)

            background_thread = threading.Thread(
                target=process_controller,
                args=[app.config['PROCESS_INTERVAL']],
                daemon=True
            )
            background_thread.start()
            app.logger.info('started background thread')

    return app

Now I can use the reloader and still the background thread only gets started once.

Upvotes: 1

Miguel Grinberg
Miguel Grinberg

Reputation: 67509

When you use the reloader there are actually two processes created. The reloader starts a master process with the only purpose of watching all the source files for changes. The reloader process runs the actual server as a child process, and when it finds that one of the source files was modified it kills the server and starts another one.

A slightly better approach for starting your thread could be to do it in a before_first_request handler. That way, only the actual server in the child process will start a thread when it gets the first request. The reloader process will never receive requests so it will never attempt to launch a thread.

Upvotes: 3

Related Questions