Ver Fonte

support concurrent users

Daniel Sheffield há 1 ano atrás
pai
commit
05b0057054
2 ficheiros alterados com 49 adições e 5 exclusões
  1. 48 5
      app/rest/pyapi.py
  2. 1 0
      app/rest/requirements.txt

+ 48 - 5
app/rest/pyapi.py

@@ -25,6 +25,7 @@ from psycopg import connect
 from psycopg.sql import SQL, Literal
 import matplotlib.pyplot as plt
 import seaborn as sns
+from multiprocessing import Lock
 from ..activities.Plot import (
     get_data,
 )
@@ -33,6 +34,8 @@ from ..data.util import(
     get_include_exclude,
     get_where_include_exclude
 )
+import matplotlib
+matplotlib.use('agg')
 
 def line(pivot, ylabel=None, xlabel=None):
     ax = sns.lineplot(data=pivot, markers=True)
@@ -178,6 +181,11 @@ def get_form(action, method, filter_data, data):
 def send_static(filename):
     return static_file(filename, root='app/rest/static')
 
+
+
+global LOCK
+LOCK = Lock()
+
 @route('/grocery/trend')
 def trend():
     _, _, path, *_ = request.urlparts
@@ -186,12 +194,33 @@ def trend():
     if request.query_string != normalized:
         return redirect(f'{path}?{normalized}')
     
+    loading = template("app/rest/loading", progress=[])
     if request.query_string in CACHE:
-        return next(CACHE[request.query_string], None)
-    
-    CACHE[request.query_string] = trend_internal(path, request.query)
+        if LOCK.acquire(block=False):
+            try:
+                return CACHE[request.query_string]["state"]
+            finally:
+                try:
+                    CACHE[request.query_string]["state"] = next(CACHE[request.query_string]["iter"])
+                except StopIteration:
+                    del CACHE[request.query_string]
+                finally:
+                    LOCK.release()
+        else:
+            return CACHE[request.query_string]["state"]
 
-    return template("app/rest/loading", progress=[])
+    if LOCK.acquire(block=False):
+        if request.query_string in CACHE:
+            LOCK.release()
+            return CACHE[request.query_string]["state"]
+        try:
+            CACHE[request.query_string] = {
+                "iter": trend_internal(path, request.query),
+                "state": loading,
+            }
+        finally:
+            LOCK.release()
+    return loading
 
 def trend_internal(path, query):
     progress = []
@@ -206,7 +235,9 @@ def trend_internal(path, query):
             progress.append({ "name": "Loading data", "status": ""})
             yield template("app/rest/loading", progress=progress)
             data = get_data(query_manager, **fields)
+
             progress[-1]["status"] = "done"
+            yield template("app/rest/loading", progress=progress)
 
             if data.empty:
                 raise abort(404, f"No data for {fields}")
@@ -217,9 +248,21 @@ def trend_internal(path, query):
             pivot.columns = pivot.columns.droplevel()
             plt.figure(figsize=[16, 9])
             line(pivot, xlabel='Time', ylabel=f'$ / {unit}')
+            
+            progress[-1]["status"] = "done"
+            yield template("app/rest/loading", progress=progress)
+            
+            progress.append({ "name": "Rendering chart", "status": ""})
+            yield template("app/rest/loading", progress=progress)
+
             f = StringIO()
             plt.savefig(f, format='svg')
             form = get_form(path, 'get', get_filter(request.query, allow=PARAMS), data)
+            
+            progress[-1]["status"] = "done"
+            yield template("app/rest/loading", progress=progress)
+            
+            final = template("app/rest/trend", form=form, svg=f.getvalue())
             resp = lambda: template("app/rest/trend", form=form, svg=f.getvalue())
 
     except HTTPError as e:
@@ -347,4 +390,4 @@ SELECT query_to_xml_and_xmlschema({inner}, false, false, ''::text)
         xml=xml
     )
 
-run(host='0.0.0.0', port=6772)
+run(host='0.0.0.0', port=6772, server='gunicorn')

+ 1 - 0
app/rest/requirements.txt

@@ -1,3 +1,4 @@
 seaborn
 psycopg[binary]
 bottle
+gunicorn