11import asyncio
2+ import datetime
23import glob
34import inspect
45import json
1415from typing import Optional
1516
1617import pkg_resources
17- from fastapi import FastAPI , Form , File , Request , Depends , BackgroundTasks , HTTPException
18+ from fastapi import FastAPI , Form , File , Request , Depends , HTTPException
1819from fastapi .responses import HTMLResponse , FileResponse
1920from fastapi .responses import RedirectResponse , JSONResponse
2021from fastapi .templating import Jinja2Templates
2122from fastapi .websockets import WebSocket
2223from pydantic import BaseModel , Field , ValidationError
2324from websockets .exceptions import ConnectionClosed
2425
25- THREADS = int (os .environ .get ('CUSTOMIZER_THREADS' , 10 ))
26+ THREADS = int (os .environ .get ('CUSTOMIZER_THREADS' , 1 ))
2627TIMEOUT = int (os .environ .get ('CUSTOMIZER_JOB_TIMEOUT' , 1200 ))
28+ QUEUESIZE = int (os .environ .get ('CUSTOMIZER_QUEUE_LENGTH' , 20 ))
29+ TEMPLATEDIR = pkg_resources .resource_filename (__name__ , 'templates' )
2730
2831# TODO: make this configurable
2932ALL_PARTS = [
4548 "MainCase/MainCaseLid" ,
4649 "MainCase/UsbCover" ,
4750]
51+
4852ALL_PARTS .extend (
4953 [f"logo/OpenBikeSensor/MainCase{ l } -{ inv } -{ mn } "
5054 for l in ["" , "Lid" ]
5155 for inv in ["inverted" , "normal" ]
5256 for mn in ["main" , "highlight" ]])
5357
54- #ALL_PARTS = ALL_PARTS[:3] # for debugging
58+ # ALL_PARTS = ALL_PARTS[:3] # for debugging
5559
5660queue = asyncio .Queue (maxsize = 20 )
5761app = FastAPI ()
58- TEMPLATEDIR = pkg_resources .resource_filename (__name__ , 'templates' )
5962
6063templates = Jinja2Templates (directory = TEMPLATEDIR )
6164
65+ job_durations = [200 , 200 , 200 ]
66+
6267
6368def field_type (entry , default_value ):
6469 if entry in ["main_case_logo_svg" , "main_case_lid_logo_svg" ]:
@@ -82,6 +87,19 @@ def models(root):
8287MODEL_ROOT = (ROOT / "src" ).resolve ()
8388
8489
90+ async def queue_runner ():
91+ while True :
92+ fun , arg = await queue .get ()
93+ logging .info (f"running { arg } " )
94+ await fun (arg )
95+
96+
97+ @app .on_event ('startup' )
98+ async def app_startup ():
99+ asyncio .create_task (queue_runner ())
100+ asyncio .create_task (queue_runner ())
101+
102+
85103def scadify (value ):
86104 if isinstance (value , bool ):
87105 return str (value ).lower ()
@@ -151,11 +169,13 @@ def package_to_zip(source: Path, target: Path):
151169 name = os .path .join (root , name )
152170 name = os .path .normpath (name )
153171 zipped_name = re .sub (r'^export/' , 'OpenBikeSensor_customized/' , name )
154- print ("NAME" , name , zipped_name )
155172 zf .write (name , zipped_name )
156173
157174
158175async def run_job (uid , parts = None ):
176+ global job_durations
177+ starttime = datetime .datetime .now ()
178+
159179 if parts == None :
160180 parts = ALL_PARTS
161181 dir_to_work = Path (tempfile .gettempdir ()) / uid
@@ -178,8 +198,6 @@ async def run_job(uid, parts=None):
178198
179199 await copy_build_files_to_build_dir (dir_to_work , temp , job_config .use_custom_logo )
180200
181- logging .error (f" run_job got { dir_to_work } " )
182-
183201 try :
184202 if job_config .use_custom_logo :
185203 parts .extend ([f"logo/CustomLogo/MainCase{ l } -{ inv } -{ mn } "
@@ -210,6 +228,8 @@ async def run_job(uid, parts=None):
210228 write_info_json ()
211229
212230 raise
231+ job_durations = job_durations [- 2 :]
232+ job_durations .append ((datetime .datetime .now () - starttime ).total_seconds ())
213233
214234
215235async def copy_build_files_to_build_dir (dir_to_work : Path , build_dir : Path , use_custom_logo : bool ):
@@ -342,7 +362,6 @@ async def jobstate(websocket: WebSocket, uid: uuid.UUID):
342362
343363@app .post ("/job" )
344364async def form_post (request : Request ,
345- background_tasks : BackgroundTasks ,
346365 main_case_logo_svg : Optional [bytes ] = File (None ),
347366 main_case_lid_logo_svg : Optional [bytes ] = File (None ),
348367 variables : CustomVariables = Depends (CustomVariables .as_form )):
@@ -361,8 +380,17 @@ async def form_post(request: Request,
361380 if len (main_case_logo_svg ) == 0 and len (main_case_lid_logo_svg ) == 0 :
362381 variables .use_custom_logo = False
363382 variables_json_file = work_dir / "variables.json"
383+
384+ open (work_dir / "log.txt" , "w" ).write (
385+ f"job queued, approx wait time to start { (sum (job_durations ) / len (job_durations )) * max (1 , queue .qsize () - 2 )} seconds\n " )
386+ open (work_dir / "info.json" , "w" ).write (json .dumps ({"parts" : ["none" ], "status" : "queued" , }))
387+
364388 variables_json_file .open ("w" ).write (variables .json ())
365- background_tasks .add_task (run_job , uid )
389+
390+ try :
391+ queue .put_nowait ((run_job , uid ))
392+ except asyncio .QueueFull :
393+ return HTTPException (status_code = 503 , detail = "Queue is full, please try again later" )
366394
367395 if "Accept" in request .headers and request .headers ["Accept" ] == "application/json" :
368396 return JSONResponse (RunningJob (uid = uid , ** variables .dict ()).dict ())
0 commit comments