@@ -69,22 +69,34 @@ async def list_roadmaps(
6969 import time
7070
7171 start_time = time .time ()
72- logger .info ("list_roadmaps: Calling service.list_catalog via executor " )
72+ logger .info ("list_roadmaps: Calling service.list_catalog" )
7373
74+ # Wrap the sync database call in executor to avoid blocking event loop
75+ # The service.list_catalog is async but calls sync DB code internally
7476 def call_db ():
7577 try :
7678 logger .info ("list_roadmaps.executor: Starting database call" )
77- return service ._result_store .list_catalog (
78- page = page ,
79- page_size = page_size ,
80- language = language ,
81- tag = tag ,
82- difficulty = difficulty ,
83- min_rating = min_rating ,
84- min_views = min_views ,
85- min_syncs = min_syncs ,
86- sort = sort ,
87- )
79+ # Use the service's internal result_store directly for executor
80+ # This avoids issues with async/sync boundaries
81+ if hasattr (service , "_result_store" ):
82+ return service ._result_store .list_catalog (
83+ page = page ,
84+ page_size = page_size ,
85+ language = language ,
86+ tag = tag ,
87+ difficulty = difficulty ,
88+ min_rating = min_rating ,
89+ min_views = min_views ,
90+ min_syncs = min_syncs ,
91+ sort = sort ,
92+ )
93+ else :
94+ # For test stubs, fallback to async service method
95+ # This won't work in executor, but should work in tests
96+ raise RuntimeError (
97+ "Service doesn't have _result_store and executor can't "
98+ "call async methods"
99+ )
88100 except Exception as e :
89101 elapsed = time .time () - start_time
90102 logger .error (
@@ -94,25 +106,42 @@ def call_db():
94106 )
95107 raise
96108
97- loop = asyncio .get_event_loop ()
98- try :
99- items , total_count = await asyncio .wait_for (
100- loop .run_in_executor (None , call_db ),
101- timeout = 25.0 , # 25 second timeout (less than Render's 30s)
102- )
103- elapsed = time .time () - start_time
104- logger .info (
105- f"list_roadmaps: Query completed in { elapsed :.2f} s - "
106- f"Retrieved { len (items )} items, total={ total_count } "
107- )
108- except asyncio .TimeoutError :
109- elapsed = time .time () - start_time
110- logger .error (
111- f"list_roadmaps: Database query timed out after { elapsed :.2f} s"
112- )
113- raise HTTPException (
114- status_code = status .HTTP_504_GATEWAY_TIMEOUT ,
115- detail = "Database query timed out" ,
109+ # Check if we have _result_store (production) or need to use async method (tests)
110+ if hasattr (service , "_result_store" ):
111+ # Production: Use executor for sync DB call
112+ loop = asyncio .get_event_loop ()
113+ try :
114+ items , total_count = await asyncio .wait_for (
115+ loop .run_in_executor (None , call_db ),
116+ timeout = 25.0 , # 25 second timeout (less than Render's 30s)
117+ )
118+ elapsed = time .time () - start_time
119+ logger .info (
120+ f"list_roadmaps: Query completed in { elapsed :.2f} s - "
121+ f"Retrieved { len (items )} items, total={ total_count } "
122+ )
123+ except asyncio .TimeoutError :
124+ elapsed = time .time () - start_time
125+ logger .error (
126+ f"list_roadmaps: Database query timed out after { elapsed :.2f} s"
127+ )
128+ raise HTTPException (
129+ status_code = status .HTTP_504_GATEWAY_TIMEOUT ,
130+ detail = "Database query timed out" ,
131+ )
132+ else :
133+ # Tests: Use async service method directly
134+ logger .info ("list_roadmaps: Using async service method (test mode)" )
135+ items , total_count = await service .list_catalog (
136+ page = page ,
137+ page_size = page_size ,
138+ language = language ,
139+ tag = tag ,
140+ difficulty = difficulty ,
141+ min_rating = min_rating ,
142+ min_views = min_views ,
143+ min_syncs = min_syncs ,
144+ sort = sort ,
116145 )
117146
118147 total_pages = math .ceil (total_count / page_size ) if total_count > 0 else 0
0 commit comments