Skip to content

[SDESK-7917] Feature: Celery async worker runtime#3225

Open
MarkLark86 wants to merge 2 commits into
superdesk:developfrom
MarkLark86:SDESK-7917
Open

[SDESK-7917] Feature: Celery async worker runtime#3225
MarkLark86 wants to merge 2 commits into
superdesk:developfrom
MarkLark86:SDESK-7917

Conversation

@MarkLark86
Copy link
Copy Markdown
Contributor

@MarkLark86 MarkLark86 commented May 29, 2026

Purpose

Originally celery workers run their tasks procedurally per thread/process, not starting the next task until the current one is finished. This causes a performance bottleneck, especially when many tasks are queued and async I/O is in use.

What has changed

  • Create CeleryAsyncWorkerThread thread class
    • tasks are executed on a separate asyncio event loop
    • limits the number of concurrent tasks on the asyncio event loop, and automatically continued once load has stabilized
    • When stopping the thread, makes sure to wait for the current tasks to complete
  • Create CeleryAsyncWorkerTask celery task, extending from our existing ones, so eager still works with the newer runtime.
  • Celery hooks, so the worker thread is started/stopped when required
  • Test celery worker app

Resolves: SDESK-7917

@MarkLark86 MarkLark86 added this to the 3.6 milestone May 29, 2026
@MarkLark86 MarkLark86 requested review from eos87 and petrjasek May 29, 2026 01:39
@MarkLark86
Copy link
Copy Markdown
Contributor Author

MarkLark86 commented May 29, 2026

@petrjasek @eos87 I created a test instance so we can test it out before merging. So far publishing seems to work 😄

I can confirm it's using the new async worker thread, from from the log entry:

May 29 05:23:47 sdpr-4158 sh[106]: 05:23:47 work.1 | on_worker_init[250] level=INFO process=ForkPoolWorker-1

Comment thread docs/settings.rst
Comment on lines +304 to +317
``CELERY_ASYNC_THREAD_MAX_TASKS``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Default: ``100``

The maximum number of active tasks in the async thread. If more tasks are added, task submission
to the thread is paused (as the client level).

``CELERY_ASYNC_THREAD_RESTART_TASKS``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Default: ``20``

The maximum number of active tasks in the async thread, before task submission is resumed.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what determines these values? I'd assume the hardware but what's the relation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default values are ones that I came up with. It is something we're going to have to try out, and might need different values depending on the environment (hardware capabilities, tasks/sec etc).

They're used for backpressure mitigation of the asyncio event loop. Without this, the event loop could become overloaded slowing down all tasks on the loop, essentially allowing unlimited number of concurrent tasks in the thread.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new “async Celery worker runtime” that offloads task execution onto a dedicated asyncio event loop thread, adds lifecycle hooks to start/stop that thread with Celery worker processes, and adds tests + configuration/docs to support the feature flag.

Changes:

  • Added CeleryAsyncWorkerThread + CeleryAsyncWorkerTask to execute Celery tasks on an asyncio loop and apply basic high/low watermark backpressure.
  • Wired Celery worker signals to start/stop the async worker thread and updated Celery initialization when CELERY_USE_ASYNC_WORKER is enabled.
  • Added background-worker integration tests and documented new Celery async worker settings.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
superdesk/celery_app/async_worker.py Introduces the async worker thread + task wrapper (core runtime behavior).
superdesk/celery_app/hooks.py Connects Celery worker lifecycle signals to start/stop the async thread.
superdesk/celery_app/__init__.py Enables the async runtime via config flag and adjusts Celery configuration.
superdesk/default_settings.py Adds new async worker settings and updates Celery serializer/protocol defaults.
superdesk/tests/worker.py Adds a test Celery app + helper to run a worker subprocess for integration tests.
tests/celery_app/async_worker.py Adds async worker integration/unit tests (including eager-path coverage).
docs/settings.rst Documents new CELERY_USE_ASYNC_WORKER and related async thread settings.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/celery_app/async_worker.py
Comment thread tests/celery_app/async_worker.py
Comment thread superdesk/tests/worker.py
Comment thread superdesk/celery_app/__init__.py
Comment thread superdesk/celery_app/hooks.py
Comment thread tests/celery_app/async_worker.py
Comment thread tests/celery_app/async_worker.py
Comment thread superdesk/celery_app/async_worker.py
Comment on lines +130 to +150
if not has_app_context():
await self.wsgi_app.app_context().push()

try:
self._num_tasks += 1
if isinstance(task, celery.Task):
result = task.run(*args, **kwargs)
if isawaitable(result):
result = await result
task.backend.mark_as_done(task.request.id, result)
return result
else:
return await task if isawaitable(task) else task
except self.app_errors as e:
logger.exception("Error running Celery task")
if isinstance(task, celery.Task):
task.backend.mark_as_failure(task.request.id, e)

return None
finally:
self._num_tasks -= 1
Comment thread superdesk/celery_app/async_worker.py
Copy link
Copy Markdown
Contributor

@eos87 eos87 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good to me. Left a couple of minor comments.

Comment thread superdesk/tests/worker.py
Comment thread tests/celery_app/async_worker.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants