3

I want to send a server send event to update the front end when a server reload happens. I know that you can auto-reload Flask by setting FLASK_ENV=development. Is there a callback like "onServerReload" that is called before or after a server refresh?

davidism
  • 121,510
  • 29
  • 395
  • 339
Kada B
  • 140
  • 1
  • 11
  • What does "update the front end" mean? – noslenkwah Nov 18 '19 at 20:37
  • Receiving the server send event and refresh the HTML page. – Kada B Nov 18 '19 at 20:43
  • See this question [here](https://stackoverflow.com/questions/12232304/how-to-implement-server-push-in-flask-framework). It goes over how to send events from the server. Reloading on the client side is easy with javascript. – noslenkwah Nov 18 '19 at 20:48
  • yeah, the push is not the problem, but the event from the server side. the link itself is useful once I have the event – Kada B Nov 18 '19 at 20:55
  • I see. Have you tried [before_first_request](https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.before_first_request)? I'm not sure how it interacts with debug enabled, but from the docs, it sounds like it will work. – noslenkwah Nov 18 '19 at 21:04
  • Thx for the suggestion. The method gets only called at client(browser) side request. So it does not get called when (auto-)reloading. – Kada B Nov 18 '19 at 21:23

2 Answers2

2

Not really a solution, but too long for a comment...

Flask actually uses Werkzeug's reloader. Here's where that phrase * Detected change in '/chat/app.py', reloading is printed out: werkzeug/_reloader.py

The idea of having a hook for this, seems to have been discussed long ago in issue 14 and issue 220 but was never implemented. The argument seems to be that when the application is actually reloaded, the entirety is run again, so:

from flask import Flask
app = Flask(__name__)
print ('I have been reloaded')
# Rest of file

On reload results in:

 * Detected change in '/chat/app.py', reloading
 * Restarting with stat
I have been reloaded

You could possibly put some code here which sends something to the stream, and wrap it in an if clause to make sure it only runs in dev. How to actually implement that probably depends on what you've already got.


I cloned this repo: github.com/jakubroztocil/chat which is a basic implementation of SSE with redis pub/sub as a broker, and made it look like:

red = redis.StrictRedis(host='redis')
# This will happen on init
red.publish('chat','Just Reloaded')

However the problem I came to was that when I published the 'Just Reloaded' message, because the app is in a state of reload, there are no subscribers to that channel (The subscription happens in the event_stream() function which is called when someone hits the /stream route.)

If you activate the reloader by saving a change to the code, and watch the browser's network tab you'll see the first stream close, and within a few seconds another stream opens to the same endpoint. The publish command happened inbetween these points, so never reached the frontend. This is a redis limitation I understand.

A workaround for this might be to have a dedicated flask server which handles just the event stream, and subscribes to a redis channel which the in-development apps also can publish to. Then the in-development apps connect to that stream from their frontend, and on a code-reload the subscription doesn't cut out because it's being served by a dedicated flask instance. The browser may complain if each server is accessed on a different port though.

v25
  • 7,096
  • 2
  • 20
  • 36
  • So basically a frontend reloader wouldn't work this way, because of a lack of connection between the browser tab an the new reloaded server..... I guess the a 'server shut down' must be registered and a tab refresh would then try to connect to the new server (mayber with a time out) – Kada B Nov 19 '19 at 00:26
  • Javascripts `EventStream` as implemented in the linked repo automatically reconnects to the `stream/` endpoint, as soon as the server reloads. No need for a tab refresh. The problem in this implementation is that when the `publish` command runs in my python example, the previous server is dead, but it's not yet serving the new `/stream` endpoint. Seconds later it does and the client connects, but due to the way redis pub/sub works, the client will never see the published message, because it wasn't subscribed when that was published. – v25 Nov 19 '19 at 00:40
2

After some research to flask werkzeug/_reloader.py: ReloaderLoop.restart_with_reloader at line 160. I find it uses os.environ["WERKZEUG_RUN_MAIN"] to check current process is whether monitor process or main server process. Once reload event is triggered, trigger_reload will be called, and simply call sys.exit(3).

So here is my way out, in development mode:

def main():
    if os.environ.get("WERKZEUG_RUN_MAIN") == "true":
        # If we want to do something **ONLY WHEN** true main serve process is started
        do_something_only_when_real_server_process_started()

    try:
        app.run(debug=True)
    except SystemExit:
        do_something_when_reload_triggered_before_process_exit() # <-- 
        sys.exit(3) # see werkzeug/_reloader.py line 184
Max
  • 463
  • 4
  • 11