From f2fb2b640222e49ed4046db0005863f8c0732e86 Mon Sep 17 00:00:00 2001
From: Arie Peterson <arie@greenhost.nl>
Date: Tue, 24 Jan 2023 16:47:44 +0100
Subject: [PATCH] Do not run initialization code concurrently

---
 backend/app.py           | 52 ++++++++++++++++++++++++----------------
 backend/requirements.txt |  1 +
 2 files changed, 32 insertions(+), 21 deletions(-)

diff --git a/backend/app.py b/backend/app.py
index ec18e9b6..62217372 100644
--- a/backend/app.py
+++ b/backend/app.py
@@ -3,6 +3,7 @@ from flask_cors import CORS
 from flask_jwt_extended import JWTManager
 import flask_migrate
 from jsonschema.exceptions import ValidationError
+from NamedAtomicLock import NamedAtomicLock
 from werkzeug.exceptions import BadRequest
 
 # These imports are required
@@ -68,27 +69,36 @@ app.logger.info("Starting dashboard backend.")
 cors = CORS(app)
 
 db.init_app(app)
-with app.app_context():
-    # We have reset the alembic migration history at Stackspin version 2.2.
-    # This checks whether we need to prepare the database to follow that
-    # change.
-    migration_reset.reset()
-flask_migrate.Migrate(app, db)
-try:
-    with app.app_context():
-        flask_migrate.upgrade()
-except Exception as e:
-    app.logger.info(f"upgrade failed: {type(e)}: {e}")
-    sys.exit(2)
-
-# We need this app context in order to talk the database, which is managed by
-# flask-sqlalchemy, which assumes a flask app context.
-with app.app_context():
-    # Load the list of apps from a configmap and store any missing ones in the
-    # database.
-    cluster_config.populate_apps()
-    # Same for the list of oauthclients.
-    cluster_config.populate_oauthclients()
+
+# We'll now perform some initialization routines. Because these have to be done
+# once at startup, not for every gunicorn worker, we take a machine-wide lock
+# for this.
+init_lock = NamedAtomicLock('dashboard_init')
+if init_lock.acquire():
+    try:
+        with app.app_context():
+            # We have reset the alembic migration history at Stackspin version 2.2.
+            # This checks whether we need to prepare the database to follow that
+            # change.
+            migration_reset.reset()
+        flask_migrate.Migrate(app, db)
+        try:
+            with app.app_context():
+                flask_migrate.upgrade()
+        except Exception as e:
+            app.logger.info(f"upgrade failed: {type(e)}: {e}")
+            sys.exit(2)
+
+        # We need this app context in order to talk the database, which is managed by
+        # flask-sqlalchemy, which assumes a flask app context.
+        with app.app_context():
+            # Load the list of apps from a configmap and store any missing ones in the
+            # database.
+            cluster_config.populate_apps()
+            # Same for the list of oauthclients.
+            cluster_config.populate_oauthclients()
+    finally:
+        init_lock.release()
 
 app.register_blueprint(api_v1)
 app.register_blueprint(web)
diff --git a/backend/requirements.txt b/backend/requirements.txt
index 9a625388..05715b0e 100644
--- a/backend/requirements.txt
+++ b/backend/requirements.txt
@@ -19,6 +19,7 @@ jinja2-base64-filters==0.1.4
 kubernetes==24.2.0
 MarkupSafe==2.1.1
 mypy-extensions==0.4.3
+NamedAtomicLock==1.1.3
 oauthlib==3.2.0
 pathspec==0.9.0
 platformdirs==2.5.1
-- 
GitLab