Reputation: 5589
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
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
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