Checklist
Mandatory Debugging Information
Optional Debugging Information
Related Issues and Possible Duplicates
Related Issues
Possible Duplicates
Environment & Settings
Celery version: 4.4.7, but bug likely present since 4.4.3
Steps to Reproduce
When using a database result backend (our case is MySQL, SQLAlchemy, pymysql but should not depend) in conjunction with database_engine_options that contains a setting unsupported by NullPool, Celery worker may refuse to start or even throw a fatal error in certain conditions with the following message:
TypeError: Invalid argument(s) 'max_overflow' sent to create_engine(), using configuration MySQLDialect_pymysql/NullPool/Engine. Please check that the keyword arguments are appropriate for this combination of components.
We hit this after upgrading from 4.3.0 to 4.4.7 but the bug (if it's considered one) is still present in master. I believe the cause to be 4695f8f .
What happens is, that in get_engine() we fall into the else branch which tries to instantiate NullPool with all arguments from database_engine_options except those that start with pool. However one of those is max_overflow, known to be only supported in QueuePool, which then causes the above mentioned error.
I understand the motivation behind the commit that introduced this behavior, but I'm not sure how to incorporate max_overflow (or potentially other QueuePool settings) into database_engine_options without the risk of hitting this error. It's that more dangerous, that it doesn't happen every time. The else branch is only executed (and the exception thrown) if we're not in a forked process (assuming preforking mode), which for example happens when a worker starts and receives a task that's expired. In that case, the worker obviously won't fork itself and will discard the task inline, attempting to store the result to the database by using the hardcoded NullPool.
I'm not sure whether we should specifically allow certain arguments instead of filtering out pool* ones, or rather be more tolerant in NullPool to accept **kwargs for all the unsupported ones (EDIT: not an option since NullPool is a foreign object), or whether this is even considered a bug at all. We circumvented the risk of this error by not using max_overflow but then, even using pool* options only relies on the internal implementation of create_engine() that silently filters them out - so I'm tempted to completely throw away database_engine_options. Another option is opting to always use NullPool which in prefork worker pool mode seems like a waste of resources.
Required Dependencies
- Minimal Python Version: N/A or Unknown
- Minimal Kombu Version: N/A or Unknown
- Minimal Broker Version: N/A or Unknown
- Minimal Celery Version: 4.4.3
Minimally Reproducible Test Case
- Use a database as a result backend, e.g.
result_backend = "db+mysql+pymysql://celery:password@mariadb/celery_task_results"
- Configure
database_engine_options = {"max_overflow": 1} for example (any setting not supported by NullPool constructor will do)
- Seed the task queue with a task that won't trigger a new worker subprocess fork, if using
prefork worker pool. For example a task with an overdue expiration time.
- The worker will reject the task while avoiding forking, but then instantiate
NullPool with an unsupported kwarg and raise an exception.
Expected Behavior
The worker handles all tasks without crashing.
Actual Behavior
The worker crashes on tasks that don't cause forking, provided that the conditions described above hold.
Checklist
masterbranch of Celery.contribution guide
on reporting bugs.
for similar or identical bug reports.
for existing proposed fixes.
to find out if the bug was already fixed in the master branch.
in this issue (If there are none, check this box anyway).
Mandatory Debugging Information
celery -A proj reportin the issue.(if you are not able to do this, then at least specify the Celery
version affected).
masterbranch of Celery.pip freezein the issue.to reproduce this bug.
Optional Debugging Information
and/or implementation.
result backend.
broker and/or result backend.
ETA/Countdown & rate limits disabled.
and/or upgrading Celery and its dependencies.
Related Issues and Possible Duplicates
Related Issues
Possible Duplicates
Environment & Settings
Celery version: 4.4.7, but bug likely present since 4.4.3
Steps to Reproduce
When using a database result backend (our case is
MySQL,SQLAlchemy,pymysqlbut should not depend) in conjunction withdatabase_engine_optionsthat contains a setting unsupported byNullPool, Celery worker may refuse to start or even throw a fatal error in certain conditions with the following message:We hit this after upgrading from
4.3.0to4.4.7but the bug (if it's considered one) is still present inmaster. I believe the cause to be 4695f8f .What happens is, that in get_engine() we fall into the
elsebranch which tries to instantiateNullPoolwith all arguments fromdatabase_engine_optionsexcept those that start withpool. However one of those ismax_overflow, known to be only supported inQueuePool, which then causes the above mentioned error.I understand the motivation behind the commit that introduced this behavior, but I'm not sure how to incorporate
max_overflow(or potentially otherQueuePoolsettings) intodatabase_engine_optionswithout the risk of hitting this error. It's that more dangerous, that it doesn't happen every time. Theelsebranch is only executed (and the exception thrown) if we're not in a forked process (assuming preforking mode), which for example happens when a worker starts and receives a task that's expired. In that case, the worker obviously won't fork itself and will discard the task inline, attempting to store the result to the database by using the hardcodedNullPool.I'm not sure whether we should specifically allow certain arguments instead of filtering out
pool*ones, or rather be more tolerant inNullPoolto accept**kwargsfor all the unsupported ones (EDIT: not an option sinceNullPoolis a foreign object), or whether this is even considered a bug at all. We circumvented the risk of this error by not usingmax_overflowbut then, even usingpool*options only relies on the internal implementation ofcreate_engine()that silently filters them out - so I'm tempted to completely throw awaydatabase_engine_options. Another option is opting to always useNullPoolwhich inpreforkworker pool mode seems like a waste of resources.Required Dependencies
Minimally Reproducible Test Case
result_backend = "db+mysql+pymysql://celery:password@mariadb/celery_task_results"database_engine_options = {"max_overflow": 1}for example (any setting not supported byNullPoolconstructor will do)preforkworker pool. For example a task with an overdue expiration time.NullPoolwith an unsupported kwarg and raise an exception.Expected Behavior
The worker handles all tasks without crashing.
Actual Behavior
The worker crashes on tasks that don't cause forking, provided that the conditions described above hold.