Search code examples
pythonflaskgoogle-app-enginecallback

Callbacks in flask app don't work when deployed on Google Cloud


I have a Flask app that uses an input form (GET method) to run some random simulation. The simulation results are stored in a global variable within the flask app, and then some initial plotly graphs are shown. A user would then be free to callback on some other parts of the simulation (stored in the global variable) and show other graphs using callbacks.

For the code see here: github-repo
And for a similar - but minimal - concept with less code involved: other github-repo (stores some random value in a global variable, callbacks to extract and display those values, no plotly involved)

Both apps work as intended on my local machine using flask's development server. However, when deployed to Google Cloud (App Engine standard environment), the callbacks, although working at first, will eventually stop working. The logs return a "TypeError: 'NoneType' object is not subscriptable" whenever a callback is triggered (which I guess means that the global variable holding the data is lost).

Probably irrelevant, but after a few seconds the graphical app will start working again without having to resubmit the form/reload the page, but it is obvious that the simulation was re-run from scratch and the original results are lost. Then it starts going into a cycle of stop and restart with a new simulation. On the other hand, the minimal app stops working entirely and that's it.

Being a complete newbie, I suspect it has to do with server-side request timeout, but I noticed that the apps stop working at a seemingly random point in time. It could be right after loading the page, or it could keep working for a minute. If it was a request timeout issue I thought I'd see some consistency (e.g. stop working always 30 seconds after the last request). I tried to investigate the google cloud docs, but (being a complete newbie), I'm in over my head trying to solve this by myself and can't figure out what's the server issue that is causing this behaviour and how I could fix it (granted that it is a server side problem). Any help or pointer would be appreciated, thanks.


Solution

  • Using the code from the stripped down example

    1. GAE creates instances (meaning could be multiple) to meet/handle traffic.

    2. Imagine that it creates an instance when you load the home page. Loading the home page initializes the global variable.

    3. When user picks a name, a GET request is made to the callback which returns a value based on the global variable. You keep on picking different names and on each occasion, the callback is triggered.

    4. However, it's possible that picking a different name which triggers the GET call to the callback causes a new instance to be created (to handle the traffic). That new instance means your code is loaded anew (fresh) into that instance which means that instance has the global variable as None. This new instance then calls the code for the callback. The global variable is NEVER inititalized because the code for the home page isn't called at all.

    A possible solution to your problem is to add the following to your app.yaml file

    automatic_scaling:
      max_instances: 1
    
    

    This means you're telling GAE to only create 1 instance of your App no matter the traffic. Doing this does away with the problem identified in the previous steps. The downside to this though is that as your traffic increases, you'll have bottlenecks and possible timeouts for some people.

    Another possible solution is to initialize the global variable outside of any of the functions i.e. have it immediately after app = Flask(__name__) . This will remove the current error of "TypeError: 'NoneType' object is not subscriptable" but you could still get other errors where your updated global variable isn't available (cos a new instance has been created and your global variable goes back to the initialized values).

    A better option to the above two is to store the value in memcache and then read from memcache each time you make a call to the callback or you pass the value to the client side and have the client side send back the value when it makes a call to the callback