Skip to content

Fix[mqb]: Re-read counter after de-configure response - #1316

Merged
dorjesinpo merged 5 commits into
mainfrom
fix/concurrent-cookie-rollback
Jun 2, 2026
Merged

Fix[mqb]: Re-read counter after de-configure response#1316
dorjesinpo merged 5 commits into
mainfrom
fix/concurrent-cookie-rollback

Conversation

@dorjesinpo

Copy link
Copy Markdown
Contributor

Concurrent cookie rollback (onOpenQueueConfirmationCookieReleased) may decrement the same count as dropHandleDispatched resulting in double decrement.
Solution: re-read current counts from handle->subStreamInfos() upon de-configure response.

@dorjesinpo
dorjesinpo requested a review from a team as a code owner April 17, 2026 15:00
@dorjesinpo dorjesinpo added the bug Something isn't working label Apr 17, 2026
@dorjesinpo dorjesinpo changed the title Re-read counter after de-configure response Fix[mqb]: Re-read counter after de-configure response Apr 17, 2026
@dorjesinpo

Copy link
Copy Markdown
Contributor Author

test_redelivery_on_primary_node_crash failure is unrelated
test_sync_if_leader_missed_rollover failure is unrelated and the fix is #1310

Comment thread src/groups/mqb/mqbblp/mqbblp_queue.cpp Outdated
const bmqp_ctrlmsg::StreamParameters& streamParameters,
mqbi::Queue* queue,
mqbi::QueueHandle* handle,

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.

Extra newline here

mqbi::QueueHandle::HandleReleasedCallback());
if (info.d_counts.d_readCount == 0) {
// Already fully released by cookie rollback — nothing to do.
}

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.

I think this is missing a return;. Else we still release the handle.

Comment thread src/groups/mqb/mqbblp/mqbblp_queue.cpp Outdated

const bool isFinal = (counter->decrement() == 0);
// Re-read current counts from the handle because cookie rollback may have
// already decremented some counts between de-confgigure request and now.

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.

de-configure

@dorjesinpo
dorjesinpo force-pushed the fix/concurrent-cookie-rollback branch 4 times, most recently from d7b5d99 to d2c5916 Compare April 28, 2026 16:17
@dorjesinpo

Copy link
Copy Markdown
Contributor Author

This PR is incomplete. We need to handle the other racer - onOpenQueueConfirmationCookieReleased

@dorjesinpo
dorjesinpo force-pushed the fix/concurrent-cookie-rollback branch 5 times, most recently from d27353a to 39fafed Compare May 29, 2026 18:34
@pniedzielski

Copy link
Copy Markdown
Contributor

Taking a look...

d_queueHandleRequesterContext_sp.createInplace(d_allocator_p,
d_allocator_p);

d_queueHandleRequesterContext_sp->setClient(this)

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.

Comment, no action needed: This fluent builder pattern here didn't fully pass the sniff test to me, because the default object constructed is not valid (hence, fluent pattern is an anti-pattern). This made me look into QueueHandleRequesterContext a little more, and I found this lovely contradiction:

/// Value-semantic type representing the context of a client, requester of a
/// queue handle.
class QueueHandleRequesterContext {

and

/// Copy constructor and assignment operator are not implemented.
QueueHandleRequesterContext&
operator=(const QueueHandleRequesterContext&) BSLS_CPP11_DELETED;

😮‍💨

From inspection of where it's used, I think we can very easily remove the invalid state from QueueHandleRequesterContext and make this type safe and impossible to misuse.

Let's get this change in as it is for the functionality change, and I'll prepare a separate PR that simplifies this.

/// the request was success and the specified `queueHandle` contains the
/// handle representing the queue that was allocated for this specified
/// `requester` session having the specified `peerInstanceId`, and the
/// `requester` session having the specified `requesterId`, and the

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.

This rename wasn't done completely:

src/groups/mqb/mqbblp/mqbblp_clusterqueuehelper.cpp
2582:    const int                           peerInstanceId)
2594:                              peerInstanceId),
2603:    const int                           peerInstanceId)
2683:                              peerInstanceId));
2743:            << ", initial peerInstanceId: " << requesterId
2744:            << ", current peerInstanceId: "
src/groups/mqb/mqbblp/mqbblp_clusterqueuehelper.h
697:                     int                                 peerInstanceId);
701:    /// specified `requester` with the specified `peerInstanceId`.  If the
710:                               const int peerInstanceId);

I think the log message particularly is confusing.

return; // RETURN
}

// TODO: revisit

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.

Can we capture this in an issue with some context? We have almost 200 TODOs in the codebase; this will get lost.

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.

Created a card "Revisit logic around NodeStatus, ElectorInfoLeaderStatus, PrimaryStatus"

.setDescription(bsl::string(description(), d_allocator_p))
.setIsClusterMember(true)
.setRequesterId(
mqbi::QueueHandleRequesterContext ::generateUniqueRequesterId())

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.

mqbi::QueueHandleRequesterContext::generateUniqueRequesterId()


// PRECONDITIONS
BSLS_ASSERT_SAFE(inDispatcherThread());
d_queueHandleRequesterContext_sp.createInplace(d_allocator_p,

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.

This is a little subtle. createQueueHandleRequesterContext pulls out the ClientIdentity and StatContext out of the old queue handle requester context, passes them into createQueueHandleRequesterContext by const&, and then calls createInplace. Are the objects these references point to still valid? Depends on whether there's any other shared_ptr to the old handle requester context, and if not, whether createInplace calls the destructor of the existing object before it calls the constructor of the new object. This isn't guaranteed, but either way it's very subtle.

I think it's better to avoid that subtle reasoning, since it's fragile next time we change this. Let's add some copies before we createInplace:

// Copy before `createInplace` destroys the old object.
const bmqp_ctrlmsg::ClientIdentity identity = d_queueHandleRequesterContext_sp->identity();
const bsl::shared_ptr<bmqst::StatContext> statContext = d_queueHandleRequesterContext_sp->statContext();

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.

On further thought, we can put this copy in createQueueHandleRequesterContext, since the ...Impl function is called in the constructor too.

bmqu::GateKeeper& gatePut();
bmqu::GateKeeper& gateConfirm();

void createQueueHandleRequesterContext();

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.

Doxygen docs ^

Signed-off-by: dorjesinpo <129227380+dorjesinpo@users.noreply.github.com>
Signed-off-by: dorjesinpo <129227380+dorjesinpo@users.noreply.github.com>
Signed-off-by: dorjesinpo <129227380+dorjesinpo@users.noreply.github.com>
Signed-off-by: dorjesinpo <129227380+dorjesinpo@users.noreply.github.com>
Signed-off-by: dorjesinpo <129227380+dorjesinpo@users.noreply.github.com>
@dorjesinpo
dorjesinpo force-pushed the fix/concurrent-cookie-rollback branch from 39fafed to 9314743 Compare June 1, 2026 19:56
@dorjesinpo
dorjesinpo requested a review from pniedzielski June 2, 2026 11:14
@dorjesinpo
dorjesinpo merged commit 0b17e3b into main Jun 2, 2026
49 checks passed
@dorjesinpo
dorjesinpo deleted the fix/concurrent-cookie-rollback branch June 2, 2026 15:04
SrinathhSatuluri pushed a commit to SrinathhSatuluri/blazingmq that referenced this pull request Aug 20, 2026
Signed-off-by: dorjesinpo <129227380+dorjesinpo@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants