Following up on the discussion I had here (Restarting sanic from an external python script), I wanted to stop using the auto_reload
option from Sanic and instead, control when the server would restart.
The main reason for that is simple: If an update made was by changing the database model, the server would fail until the DB migration was done.
Right now, my updating script works as follows:
When changing the code in my “prod” branch:
- The code is automatically pulled on the production server.
- Any package present in requiremetns.txt are automatically added or updated
- The database is updated, using Alembic
Previously, this was causing trouble because:
- If any new package was not installed, Sanic would restart and fail because of the missing package.
- The restart would succeed, but the database model present in the code would not match the database since the
upgrade
command was not sent yet, causing SQL issues until step 3 was reached.
In order to avoid this, I’ve made a script that listens to one specific file modification time (“reload”), and only restarts Sanic when that file is touched.
With this, I’ve added a 4th step on my above list, which is simply to do a touch on that file as the last step, triggering the restart when everything is ready.
Here’s the code I’ve come up, please share your insight, if you have any better suggestions:
from sanic import Sanic
from sanic.log import logger
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
import os, signal
class ModificationEventLoader(FileSystemEventHandler):
def __init__(self, handler):
self.handler = handler
def on_modified(self, event):
if event.is_directory:
return
self.handler.restart()
class AutoReload:
def __init__(self, auto_reload):
self.first_loop = True
self.is_restarting = False
self.observer = None
if auto_reload:
logger.debug('Auto reload is already enabled, no need to setup the watchdog')
return
reload_file_path = os.path.join(Sanic.get_app().ctx.root_path, 'reload')
if not os.path.isfile(reload_file_path):
logger.info('Missing reload file. Auto reload will be disabled')
return None
self.observer = Observer()
self.observer.schedule(ModificationEventLoader(self), reload_file_path, recursive=False)
self.observer.start()
def restart(self):
self.is_restarting = True
os.kill(os.getpid(), signal.SIGINT)
def run(self):
if self.first_loop:
self.first_loop = False
return True
if self.is_restarting:
self.is_restarting = False
return True
if self.observer:
self.observer.stop()
self.observer.join()
return False
In order to make this to work, you’ll have to wrap your app.run(**options)
to:
params = {
'host': '127.0.0.1',
'port': 8000,
'debug': True,
'access_log': True,
'auto_reload': False
}
auto_reloader = AutoReload(params['auto_reload'])
while auto_reloader.run():
# Running server
app.run(**params)
The AutoLoader.run
function will only return True
at the first loop (starting) and when the “reload” file was changed, otherwise, it will return False (and stop the watchdog).
Note: This requires the watchdog
package to handle the changes asynchronously. I’ve looked onto how Sanic handles file change when auto_reload
is set to True
, but it’s a while True
with a time.sleep()
which blocks the rest of the code. That wasn’t possible here.
Hope this helps!