Add manual task acknowledgement#637
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #637 +/- ##
==========================================
+ Coverage 80.02% 80.20% +0.18%
==========================================
Files 69 69
Lines 2548 2577 +29
==========================================
+ Hits 2039 2067 +28
- Misses 509 510 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
I love to see this feature. Also it shines along with per-task ack support.
So, for example I can see a scenario for a long-running tasks, where you can do something like:
taskiq worker --ack-type=when_executed
@broker.task(ack_type="manual")
async def testing(context):
# Long-running stuff
await context.ack()But there is a small problem, which I see. Not with this pull request tho, but there is one piece missing.
For long-running tasks we need to tell our broker to not send the message we're processing right now to any other subscriber.
For example in nats there is a special method for this:
AckProgress: https://nats-io.github.io/nats.net/api/NATS.Client.JetStream.NatsJSMsg-1.AckProgressAsync.html
What do you think?
|
Yes, for Redis streams this can happen because taskiq-redis uses XAUTOCLAIM for messages idle longer than idle_timeout. I think this is a separate broker-level progress/heartbeat problem. Manual ack gives task code control over final acknowledgement, but long-running tasks may also need a way to extend/reset the pending idle timeout before the final ack. We could discuss a follow-up API like context.ack_progress() / context.extend_ack_timeout(), implemented only by brokers that support it. |
|
Yeah, that sounds good to me. |
|
I just asked Codex to investigate how this maps to the existing brokers, especially NATS JetStream already supports this. In await message.in_progress()It sends the JetStream Current state in Both AckableMessage(
data=nats_message.data,
ack=nats_message.ack,
)So taskiq can call the final ack, but it cannot currently send progress ack / heartbeat while the task is still running. Possible follow-up implementation: Extend class AckableMessage(BaseModel):
data: bytes
ack: Callable[[], None | Awaitable[None]]
ack_progress: Callable[[], None | Awaitable[None]] | None = NoneExpose it from task context: await context.ack_progress()And wire it in AckableMessage(
data=nats_message.data,
ack=nats_message.ack,
ack_progress=nats_message.in_progress,
)For brokers that do not support this, Then long-running tasks could do something like: @broker.task(ack_type="manual")
async def long_task(context: Context) -> None:
while still_working:
await do_some_work()
await context.ack_progress()
await context.ack()So I think NATS would be a good first broker to support this API, because it maps directly to JetStream’s native progress ack. |
Why
Some broker backends support explicit message acknowledgement, but taskiq currently only lets workers choose a fixed automatic acknowledgement point: when received, when executed, or when saved.
For some tasks, acknowledgement needs to happen after an application-level durable action is completed, but before the whole task finishes. Examples include committing an external transaction, persisting an idempotency marker, or coordinating with another system where the task code knows the correct acknowledgement boundary better than the worker.
This change adds an explicit
manualacknowledgement mode so advanced users can acknowledge from inside the task throughContext.ack(), while preserving the existing automatic acknowledgement behavior for all other modes.Summary
manualacknowledgement typeContext.ack(),Context.is_ackable, andContext.is_ackedfor task-level manual acknowledgementCompatibility
Existing acknowledgement modes are unchanged. Manual acknowledgement only works for brokers that yield
AckableMessage; otherwiseContext.ack()raises a clear runtime error.Tests
env -u PYTHONPATH -u VIRTUAL_ENV .venv/bin/python -m pytest -qenv -u PYTHONPATH -u VIRTUAL_ENV .venv/bin/python -m ruff check .env -u PYTHONPATH -u VIRTUAL_ENV .venv/bin/python -m black --check .