33from typing import Optional
44
55from crontab import CronTab
6+ from django .conf import settings
67from django .core .exceptions import ValidationError
78from django .utils .module_loading import import_string
89
10+ from steady_queue .db_router import steady_queue_database_alias
911from steady_queue .processes .base import Base
1012
1113
@@ -167,6 +169,7 @@ def schedulers(self) -> list["Configuration.Process"]:
167169 def is_valid (self ):
168170 self .errors = []
169171 self .errors .extend (self .validate_configured_processes ())
172+ self .errors .extend (self .validate_database_pool_size ())
170173 self .errors .extend (self .validate_recurring_tasks ())
171174
172175 return len (self .errors ) == 0
@@ -177,6 +180,39 @@ def validate_configured_processes(self) -> list[ValidationError]:
177180
178181 return []
179182
183+ def validate_database_pool_size (self ) -> list [ValidationError ]:
184+ # Match Solid Queue behavior by validating worker thread count against
185+ # the queue DB connection pool size when a max_size is explicitly set.
186+ if len (self .options .workers ) == 0 :
187+ return []
188+
189+ db_alias = steady_queue_database_alias ()
190+ db_config = settings .DATABASES .get (db_alias , {})
191+
192+ if db_config .get ("ENGINE" ) != "django.db.backends.postgresql" :
193+ return []
194+
195+ pool_options = db_config .get ("OPTIONS" , {}).get ("pool" )
196+ if not isinstance (pool_options , dict ):
197+ return []
198+
199+ pool_max_size = pool_options .get ("max_size" )
200+ if not isinstance (pool_max_size , int ):
201+ return []
202+
203+ if pool_max_size < self .estimated_number_of_threads :
204+ return [
205+ ValidationError (
206+ "Steady Queue is configured to use "
207+ f"{ self .estimated_number_of_threads } threads but the "
208+ f"database connection pool max_size for '{ db_alias } ' is "
209+ f"{ pool_max_size } . Increase "
210+ f"DATABASES['{ db_alias } ']['OPTIONS']['pool']['max_size']."
211+ )
212+ ]
213+
214+ return []
215+
180216 def validate_recurring_tasks (self ) -> list [ValidationError ]:
181217 if self .skip_recurring :
182218 return []
@@ -195,6 +231,13 @@ def validate_recurring_tasks(self) -> list[ValidationError]:
195231
196232 return errors
197233
234+ @property
235+ def estimated_number_of_threads (self ) -> int :
236+ # At most `threads` in each worker + 2 additional threads (worker loop
237+ # and heartbeat), mirroring Solid Queue's sizing heuristic.
238+ max_worker_threads = max ((w .threads for w in self .options .workers ), default = 1 )
239+ return max_worker_threads + 2
240+
198241 @property
199242 def skip_recurring (self ) -> bool :
200243 return self .options .skip_recurring or self .options .only_work
0 commit comments