1+ import random
12from typing import (
23 Union ,
34 Optional ,
@@ -38,8 +39,8 @@ class BackgroundQueue:
3839
3940 TODO:
4041 - Add Task Timeout
41- - Add Task Retry
42- - Added Wrapper Support
42+ - Add Task Retry (done)
43+ - Added Wrapper Support (done)
4344 """
4445 service_name : str = SERVICE_NAME
4546
@@ -219,7 +220,6 @@ async def _execute_callable(self, func: Callable, *args, **kwargs):
219220 result = None
220221 try :
221222 loop = asyncio .get_running_loop ()
222- # with ThreadPoolExecutor(max_workers=1) as executor:
223223 result = await loop .run_in_executor (
224224 self .executor ,
225225 func ,
@@ -281,11 +281,8 @@ async def process_queue(self):
281281 f"Invalid Function { func } in Queue"
282282 )
283283 continue
284- except Exception as e : # Catch all exceptions
285- print ('ERROR > ' , e )
286- self .logger .error (
287- f"Error executing task { func .__name__ } : { e } "
288- )
284+ except Exception as exc : # Catch all exceptions
285+ await self ._handle_failure (task , exc )
289286 continue
290287 finally :
291288 if self ._enable_profiling is True :
@@ -305,7 +302,7 @@ async def process_queue(self):
305302 Peak Memory Usage: { peak_memory / (1024 ** 2 ):.2f} MB
306303 """ )
307304 except Exception as e :
308- print ('LOG ERROR > ' , e )
305+ print ('TASK LOG ERROR > ' , e )
309306 # Call your task completion callback (if any)
310307 try :
311308 await self ._callback (task , result = result )
@@ -330,6 +327,41 @@ async def fire_consumers(self):
330327 )
331328 self .consumers .append (task )
332329
330+ async def _requeue (self , task : TaskWrapper , exc : Exception ) -> None :
331+ """Internal: re-enqueues `task` after updating retry-counters."""
332+ task .retries_done += 1
333+ # optional exponential back-off
334+ if task .retry_delay :
335+ await asyncio .sleep (
336+ random .uniform (0.8 , 1.2 ) * task .retry_delay * task .retries_done
337+ )
338+
339+ self .logger .warning (
340+ f"Retry { task .retries_done } /{ task .max_retries } for { task !r} "
341+ f"after error: { exc } "
342+ )
343+ if hasattr (task , "tracker" ):
344+ # set status = "retrying"
345+ await task .tracker .set_running (task .task_uuid )
346+ await self .queue .put (task )
347+
348+ async def _handle_failure (
349+ self ,
350+ task : Any ,
351+ exc : Exception
352+ ) -> None :
353+ """Central place that decides whether we retry or finally give up."""
354+ if (
355+ isinstance (task , TaskWrapper ) and task .retries_done < task .max_retries
356+ ):
357+ await self ._requeue (task , exc )
358+ else :
359+ self .logger .error (
360+ f"Task { task !r} failed permanently after "
361+ f"{ getattr (task , 'retries_done' , 0 )} attempt(s)."
362+ )
363+ await self ._callback (task , result = dict (status = "failed" , error = exc ))
364+
333365
334366class BackgroundTask :
335367 """BackgroundTask.
0 commit comments