Skip to content

[ISSUE #9583] Fix ack not working after changeInvisibleDuration in Proxy#10259

Closed
LiyunZhang10 wants to merge 2 commits intoapache:developfrom
LiyunZhang10:fix-ack-after-change-invisible-duration
Closed

[ISSUE #9583] Fix ack not working after changeInvisibleDuration in Proxy#10259
LiyunZhang10 wants to merge 2 commits intoapache:developfrom
LiyunZhang10:fix-ack-after-change-invisible-duration

Conversation

@LiyunZhang10
Copy link
Copy Markdown

Which Issue(s) This PR Fixes

Brief Description

After changeInvisibleDuration succeeds, ChangeInvisibleDurationActivity removes the old receipt handle from ReceiptHandleManager (line 53) but never re-registers the new handle returned by the broker. This causes two problems:

  1. Ack fails silently: When the client sends ack using the new handle, the Proxy's AckMessageActivity.getHandleString() calls removeReceiptHandle() which returns null (the new handle was never stored). The ack then goes directly to the broker with only the client-side handle, bypassing the Proxy's managed handle path.

  2. Auto-renew stops: The ReceiptHandleManager.scheduleRenewTask() mechanism loses track of the message entirely, since the handle was removed but never replaced. If the new invisible duration expires before the ack reaches the broker through the revive topic, the message gets redelivered — causing the duplicate consumption reported in [Bug] ack doesn't work after changeInvisibleDuration #9583.

Changes

  • In ChangeInvisibleDurationActivity.convertToChangeInvisibleDurationResponse(), after a successful response, decode the new receipt handle and re-register it into ReceiptHandleManager via addReceiptHandle()
  • Added unit test testNewHandleIsReRegisteredAfterSuccess to verify the new handle is properly registered after a successful changeInvisibleDuration

… in Proxy

After changeInvisibleDuration succeeds, ChangeInvisibleDurationActivity
removes the old receipt handle from ReceiptHandleManager but never
re-registers the new handle returned by the broker. This causes:
1. Subsequent ack cannot find the updated handle in the Proxy's local
   receipt handle store, bypassing the managed handle path
2. The auto-renew mechanism loses track of the message, so if the new
   invisible duration expires before ack is processed, the message gets
   redelivered

Fix: after a successful changeInvisibleDuration, re-register the new
receipt handle into ReceiptHandleManager via addReceiptHandle(), so
that subsequent ack and auto-renew work correctly with the new handle.

Made-with: Cursor
MessageReceiptHandle newMessageReceiptHandle = new MessageReceiptHandle(
group, topic, newHandle.getQueueId(), newHandleStr,
request.getMessageId(), newHandle.getOffset(), 0);
messagingProcessor.addReceiptHandle(ctx,
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.

should only add this when auto renew is turn on

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

您好,已修复

@qianye1001
Copy link
Copy Markdown
Contributor

Thanks for the PR. I have a question about the underlying assumption here.

Could you elaborate on which gRPC client scenario would lead to a changeInvisibleDuration call followed by an ack on the same message? As far as I understand the current client SDK designs (both Java and other language implementations), this sequence isn't a well-established pattern:

  • For PushConsumer, the SDK handles ack/nack internally after the message listener returns. There's no public API for the user to call changeInvisibleDuration mid-processing and then explicitly ack.
  • For SimpleConsumer, while the user does have access to both changeInvisibleDuration and ack APIs, the typical intended use of changeInvisibleDuration is to extend processing time — the subsequent ack would be the natural follow-up, but it's worth noting that changeInvisibleDuration on the broker side essentially performs an ack-then-re-enqueue (creating a new CK timer message). The new receipt handle returned is meant for further changeInvisibleDuration calls or a final ack, but is there a documented contract guaranteeing that the proxy must maintain handle continuity across this transition?

My concern is: if no existing client SDK actually relies on calling changeInvisibleDuration followed by ack through the proxy's receipt handle management path, then this fix might be papering over a gap that should instead be addressed at the protocol/contract level. Could you provide a concrete client-side code path (from one of the official SDKs) that demonstrates this sequence passing through the proxy's ReceiptHandleManager?

@LiyunZhang10
Copy link
Copy Markdown
Author

LiyunZhang10 commented Apr 22, 2026

Thanks for the PR. I have a question about the underlying assumption here.

Could you elaborate on which gRPC client scenario would lead to a changeInvisibleDuration call followed by an ack on the same message? As far as I understand the current client SDK designs (both Java and other language implementations), this sequence isn't a well-established pattern:

  • For PushConsumer, the SDK handles ack/nack internally after the message listener returns. There's no public API for the user to call changeInvisibleDuration mid-processing and then explicitly ack.
  • For SimpleConsumer, while the user does have access to both changeInvisibleDuration and ack APIs, the typical intended use of changeInvisibleDuration is to extend processing time — the subsequent ack would be the natural follow-up, but it's worth noting that changeInvisibleDuration on the broker side essentially performs an ack-then-re-enqueue (creating a new CK timer message). The new receipt handle returned is meant for further changeInvisibleDuration calls or a final ack, but is there a documented contract guaranteeing that the proxy must maintain handle continuity across this transition?

My concern is: if no existing client SDK actually relies on calling changeInvisibleDuration followed by ack through the proxy's receipt handle management path, then this fix might be papering over a gap that should instead be addressed at the protocol/contract level. Could you provide a concrete client-side code path (from one of the official SDKs) that demonstrates this sequence passing through the proxy's ReceiptHandleManager?

和 ack 无关,CID 后新 handle 没写回 manager,scheduler 停续期,broker 就重投(#9583),和客户端调不调 ack 无关。Proxy 内部续期路径(DefaultReceiptHandleManager.java:215-219)本来就把新 handle 写回 manager,这个 PR 只是补齐用户入口这条同样的路径。

@qianye1001
Copy link
Copy Markdown
Contributor

qianye1001 commented Apr 22, 2026

SimpleConsumer 不会发起带 AutoRenew 的 receive,所以proxy不会保存handle

PushConsumer 的 receive 有 AutoRenew,所以 proxy 会保存handle,但是同时,PushConsumer 只有在希望消息进入重试时会发起 changeInversibleTime,所以不应该继续执行 AutoRenew

所以你提到的问题不存在,如果修改,反而会影响正常的客户端的重试行为。

此外,我们正在评估关闭 Proxy 对 grpc 客户端的请求 AutoRenew 的能力。这个能力的副作用往往大于收益

@LiyunZhang10
Copy link
Copy Markdown
Author

SimpleConsumer 不会发起带 AutoRenew 的 receive,所以proxy不会保存handle

PushConsumer 的 receive 有 AutoRenew,所以 proxy 会保存handle,但是同时,PushConsumer 只有在希望消息进入重试时会发起 changeInversibleTime,所以不应该继续执行 AutoRenew

所以你提到的问题不存在,如果修改,反而会影响正常的客户端的重试行为。

此外,我们正在评估关闭 Proxy 对 grpc 客户端的请求 AutoRenew 的能力。这个能力的副作用往往大于收益

感谢指正,我关掉pr了先。

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.

[Bug] ack doesn't work after changeInvisibleDuration

3 participants