Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

[BUG] subscribe_with_handler stops silently: async task can die or be collected, sync retry path exits, handler errors reconnect the stream

未关闭
#1,233 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
3-5 天
新手友好度
65/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
python
领域
api, backend

调研方向

Start with dapr/aio/clients/grpc/client.py#L580-L599 and dapr/clients/grpc/client.py#L628-L654, then read subscription.py#L70-L74 and the commented test in tests/clients/test_dapr_grpc_client_async.py#L424-L477. Restore async coverage and add sync and async tests for failed-then-successful reconnects and a handler that raises once. Done means subscriptions keep retrying, handler errors do not reconnect the stream, async tasks are retained and awaited on close, and caller closure remains final.

由索引模型根据 Issue 内容生成。

描述

kind/bug

Expected Behavior

A subscription made with subscribe_with_handler (sync or async) keeps delivering messages to the handler until the caller closes it. If the stream drops, it reconnects, and it keeps retrying while the sidecar is unavailable. An exception raised by the handler does not tear down the stream. The returned close function stops the handler before it returns.

Actual Behavior

Links are to main at 03eebe1.

Async client: dapr/aio/clients/grpc/client.py#L580-L599

  1. No reference is kept to the task. It calls asyncio.create_task(stream_messages(subscription)) and discards the result. Python's docs warn that the event loop keeps only a weak reference to such a task, so it can be garbage-collected while it is still running. The subscription then stops with no error.
  2. Any error ends the subscription. The loop catches only StreamInactiveError. If the handler raises, or the stream raises StreamCancelledError or a non-retryable gRPC error, the task dies. Nothing reconnects, and the error only appears later as an "exception was never retrieved" warning. The sync client reconnects in that case.
  3. The close function doesn't wait for the task. close_subscription() closes the subscription but never awaits the task, so the handler can still be running after await close_fn() returns.
  4. No test covers it. test_subscribe_topic_with_handler is commented out: tests/clients/test_dapr_grpc_client_async.py#L424-L477.

Sync client: dapr/clients/grpc/client.py#L628-L654

  1. The "reconnect failed, back off and retry" branch never retries.

    • On a stream error the loop calls sub.reconnect_stream().
    • reconnect_stream() marks the stream inactive first. Then it waits for the sidecar and calls start().
    • If that wait or start() raises, the loop sleeps 5 seconds and runs continue.
    • The next for message in sub calls next_message(). The stream is still inactive, so that raises StreamInactiveError, and the loop treats it as a close and breaks.

    So a sidecar outage longer than the health wait ends the subscription for good, with no error.

  2. Handler errors reconnect a healthy stream. The same except Exception also catches exceptions raised by handler_fn. One failing handler call tears down and reconnects the stream, and the message is never acked, so it is delivered again.

Steps to Reproduce the Problem

  • 5: stop the sidecar while a subscribe_with_handler subscription is running. Keep it down for longer than DaprHealth.wait_for_sidecar waits, then start it again. The handler thread has exited, and no new messages reach the handler.
  • 6: have the handler raise for one message. The log shows a stream reconnect, and the message is delivered again.
  • 2: do the same with the async client. The task ends, and no further messages are handled.

Suggested fix

Build on #1231, which makes close() final: a subscription that has been closed can no longer be reopened by a reconnect.

  • Sync (5): after a failed reconnect, retry the reconnect with the backoff. Leave the loop only when the subscription was closed by the caller.
  • Both (6): treat a handler exception separately from a stream error. Log it and respond with retry, instead of reconnecting the stream.
  • Async (1–3):
    • Keep a reference to the task.
    • Handle StreamCancelledError and stream errors the way the sync client does.
    • Have the close function await the task, with a timeout.
  • Tests: restore the async handler test. Add tests for both clients: a reconnect that fails and then succeeds, and a handler that raises once.

Release Note

RELEASE NOTE: FIX subscribe_with_handler keeps retrying while the sidecar is unavailable, does not reconnect on handler errors, and the async version no longer stops silently.

主要语言
Python
星标
272
派生
152
平均合并
2 天 22 小时
30 天内合并 PR
7

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

dapr/python-sdk 的其他 Issue

查看 dapr/python-sdk 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。