@@ -44,6 +44,7 @@ def __init__(
4444 worker_concurrency : Optional [int ] = None ,
4545 priority_enabled : bool = False ,
4646 partition_queue : bool = False ,
47+ polling_interval_sec : float = 1.0 ,
4748 ) -> None :
4849 if (
4950 worker_concurrency is not None
@@ -53,12 +54,15 @@ def __init__(
5354 raise ValueError (
5455 "worker_concurrency must be less than or equal to concurrency"
5556 )
57+ if polling_interval_sec <= 0.0 :
58+ raise ValueError ("polling_interval_sec must be positive" )
5659 self .name = name
5760 self .concurrency = concurrency
5861 self .worker_concurrency = worker_concurrency
5962 self .limiter = limiter
6063 self .priority_enabled = priority_enabled
6164 self .partition_queue = partition_queue
65+ self .polling_interval_sec = polling_interval_sec
6266 from ._dbos import _get_or_create_dbos_registry
6367
6468 registry = _get_or_create_dbos_registry ()
@@ -108,50 +112,102 @@ async def enqueue_async(
108112 return await start_workflow_async (dbos , func , self .name , False , * args , ** kwargs )
109113
110114
111- def queue_thread (stop_event : threading .Event , dbos : "DBOS" ) -> None :
112- polling_interval = 1.0
113- min_polling_interval = 1.0
114- max_polling_interval = 120.0
115+ def queue_worker_thread (
116+ stop_event : threading .Event , dbos : "DBOS" , queue : Queue
117+ ) -> None :
118+ """Worker thread for processing a single queue."""
119+ polling_interval = queue .polling_interval_sec
120+ min_polling_interval = queue .polling_interval_sec
121+ max_polling_interval = max (queue .polling_interval_sec , 120.0 )
122+
115123 while not stop_event .is_set ():
116124 # Wait for the polling interval with jitter
117125 if stop_event .wait (timeout = polling_interval * random .uniform (0.95 , 1.05 )):
118126 return
119- queues = dict (dbos ._registry .queue_info_map )
120- for _ , queue in queues .items ():
121- try :
122- if queue .partition_queue :
123- dequeued_workflows = []
124- queue_partition_keys = dbos ._sys_db .get_queue_partitions (queue .name )
125- for key in queue_partition_keys :
126- dequeued_workflows += dbos ._sys_db .start_queued_workflows (
127- queue ,
128- GlobalParams .executor_id ,
129- GlobalParams .app_version ,
130- key ,
131- )
132- else :
133- dequeued_workflows = dbos ._sys_db .start_queued_workflows (
134- queue , GlobalParams .executor_id , GlobalParams .app_version , None
135- )
136- for id in dequeued_workflows :
137- execute_workflow_by_id (dbos , id )
138- except OperationalError as e :
139- if isinstance (
140- e .orig , (errors .SerializationFailure , errors .LockNotAvailable )
141- ):
142- # If a serialization error is encountered, increase the polling interval
143- polling_interval = min (
144- max_polling_interval ,
145- polling_interval * 2.0 ,
146- )
147- dbos .logger .warning (
148- f"Contention detected in queue thread for { queue .name } . Increasing polling interval to { polling_interval :.2f} ."
127+
128+ try :
129+ if queue .partition_queue :
130+ dequeued_workflows = []
131+ queue_partition_keys = dbos ._sys_db .get_queue_partitions (queue .name )
132+ for key in queue_partition_keys :
133+ dequeued_workflows += dbos ._sys_db .start_queued_workflows (
134+ queue ,
135+ GlobalParams .executor_id ,
136+ GlobalParams .app_version ,
137+ key ,
149138 )
150- else :
151- dbos .logger .warning (f"Exception encountered in queue thread: { e } " )
152- except Exception as e :
153- if not stop_event .is_set ():
154- # Only print the error if the thread is not stopping
155- dbos .logger .warning (f"Exception encountered in queue thread: { e } " )
139+ else :
140+ dequeued_workflows = dbos ._sys_db .start_queued_workflows (
141+ queue , GlobalParams .executor_id , GlobalParams .app_version , None
142+ )
143+ for id in dequeued_workflows :
144+ execute_workflow_by_id (dbos , id )
145+ except OperationalError as e :
146+ if isinstance (
147+ e .orig , (errors .SerializationFailure , errors .LockNotAvailable )
148+ ):
149+ # If a serialization error is encountered, increase the polling interval
150+ polling_interval = min (
151+ max_polling_interval ,
152+ polling_interval * 2.0 ,
153+ )
154+ dbos .logger .warning (
155+ f"Contention detected in queue thread for { queue .name } . Increasing polling interval to { polling_interval :.2f} ."
156+ )
157+ else :
158+ dbos .logger .warning (
159+ f"Exception encountered in queue thread for { queue .name } : { e } "
160+ )
161+ except Exception as e :
162+ if not stop_event .is_set ():
163+ # Only print the error if the thread is not stopping
164+ dbos .logger .warning (
165+ f"Exception encountered in queue thread for { queue .name } : { e } "
166+ )
167+
156168 # Attempt to scale back the polling interval on each iteration
157169 polling_interval = max (min_polling_interval , polling_interval * 0.9 )
170+
171+
172+ def queue_thread (stop_event : threading .Event , dbos : "DBOS" ) -> None :
173+ """Main queue manager thread that spawns and monitors worker threads for each queue."""
174+ queue_threads : dict [str , threading .Thread ] = {}
175+ check_interval = 1.0 # Check for new queues every second
176+
177+ while not stop_event .is_set ():
178+ # Check for new queues
179+ current_queues = dict (dbos ._registry .queue_info_map )
180+
181+ # Start threads for new queues
182+ for queue_name , queue in current_queues .items ():
183+ if (
184+ queue_name not in queue_threads
185+ or not queue_threads [queue_name ].is_alive ()
186+ ):
187+ thread = threading .Thread (
188+ target = queue_worker_thread ,
189+ args = (stop_event , dbos , queue ),
190+ name = f"queue-worker-{ queue_name } " ,
191+ daemon = True ,
192+ )
193+ thread .start ()
194+ queue_threads [queue_name ] = thread
195+ dbos .logger .debug (f"Started worker thread for queue: { queue_name } " )
196+
197+ # Wait for the check interval or stop event
198+ if stop_event .wait (timeout = check_interval ):
199+ break
200+
201+ # Join all queue worker threads
202+ dbos .logger .info ("Stopping queue manager, joining all worker threads..." )
203+ for queue_name , thread in queue_threads .items ():
204+ if thread .is_alive ():
205+ thread .join (timeout = 10.0 ) # Give each thread 10 seconds to finish
206+ if thread .is_alive ():
207+ dbos .logger .debug (
208+ f"Queue worker thread for { queue_name } did not stop in time"
209+ )
210+ else :
211+ dbos .logger .debug (
212+ f"Queue worker thread for { queue_name } stopped successfully"
213+ )
0 commit comments