Skip to content

Commit 07ab224

Browse files
committed
Fix[mqb]: handle same qId + different uri openQueue
Signed-off-by: Evgeny Malygin <emalygin@bloomberg.net>
1 parent 34817b2 commit 07ab224

2 files changed

Lines changed: 123 additions & 16 deletions

File tree

src/groups/mqb/mqba/mqba_clientsession.t.cpp

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -553,9 +553,13 @@ class MyMockDomain : public mqbmock::Domain {
553553
mqbmock::Dispatcher* d_mockDispatcher;
554554
bmqp_ctrlmsg::RoutingConfiguration d_routingConfiguration;
555555
bsl::shared_ptr<MyMockQueueHandle> d_queueHandle;
556-
MyQueueEngine d_mockQueueEngine;
557-
const bool d_atMostOnce;
558-
bslma::Allocator* d_allocator_p;
556+
// Previously created handles, kept alive so that their addresses remain
557+
// distinct across successive `openQueue` calls (tests relying on handle
558+
// pointer identity, e.g. duplicate-queueId, depend on this).
559+
bsl::vector<bsl::shared_ptr<MyMockQueueHandle> > d_retiredHandles;
560+
MyQueueEngine d_mockQueueEngine;
561+
const bool d_atMostOnce;
562+
bslma::Allocator* d_allocator_p;
559563

560564
// CREATORS
561565

@@ -567,6 +571,7 @@ class MyMockDomain : public mqbmock::Domain {
567571
: mqbmock::Domain(cluster, allocator)
568572
, d_mockDispatcher(dispatcher)
569573
, d_queueHandle()
574+
, d_retiredHandles(allocator)
570575
, d_mockQueueEngine(allocator)
571576
, d_atMostOnce(atMostOnce)
572577
, d_allocator_p(allocator)
@@ -595,6 +600,12 @@ class MyMockDomain : public mqbmock::Domain {
595600
queue->_setAtMostOnce(d_atMostOnce);
596601
queue->_setDispatcher(d_mockDispatcher);
597602

603+
// Retain the previously created handle so its address is not reused by
604+
// the handle created below.
605+
if (d_queueHandle) {
606+
d_retiredHandles.push_back(d_queueHandle);
607+
}
608+
598609
d_queueHandle.createInplace(d_allocator_p,
599610
queue,
600611
clientContext,
@@ -2262,6 +2273,79 @@ static void test11_initiateShutdown()
22622273
}
22632274
}
22642275

2276+
static void test12_openQueueDuplicateQueueId()
2277+
// ------------------------------------------------------------------------
2278+
// TESTS OPEN QUEUE WITH A DUPLICATE QUEUE ID
2279+
//
2280+
// Concerns:
2281+
// - A misbehaving client may send a second openQueue request reusing a
2282+
// queueId that is already associated with a different queue. The
2283+
// broker must reject that request gracefully (returning an error to
2284+
// the client).
2285+
//
2286+
// Plan:
2287+
// Instantiate a testbench, open a first queue with a given queueId, then
2288+
// send a second openQueue for a *different* URI reusing the same
2289+
// queueId. Verify the first response is a successful OpenQueueResponse
2290+
// and the second is an E_INVALID_ARGUMENT Status.
2291+
//
2292+
// Testing:
2293+
// That reusing a queueId for a different queue is rejected.
2294+
// ------------------------------------------------------------------------
2295+
{
2296+
bmqtst::TestHelper::printTestName(
2297+
"TESTS OPEN QUEUE WITH A DUPLICATE QUEUE ID");
2298+
2299+
const bsl::string uri1("bmq://my.domain/queue-first",
2300+
bmqtst::TestHelperUtil::allocator());
2301+
const bsl::string uri2("bmq://my.domain/queue-second",
2302+
bmqtst::TestHelperUtil::allocator());
2303+
const int queueId = 1;
2304+
2305+
TestBench tb(client(e_FirstHop),
2306+
false, // atMostOnce
2307+
bmqtst::TestHelperUtil::allocator());
2308+
2309+
// Open the first queue: expect a successful OpenQueueResponse.
2310+
tb.openQueue(uri1, queueId);
2311+
tb.d_cs.flush();
2312+
2313+
{
2314+
bmqio::TestChannel::WriteCall writeCall;
2315+
BMQTST_ASSERT(tb.d_channel->getWriteCall(&writeCall, 0));
2316+
2317+
bmqp::Event event(&writeCall.d_blob,
2318+
bmqtst::TestHelperUtil::allocator());
2319+
BMQTST_ASSERT(event.isControlEvent());
2320+
2321+
bmqp_ctrlmsg::ControlMessage response(
2322+
bmqtst::TestHelperUtil::allocator());
2323+
BMQTST_ASSERT_EQ(0, event.loadControlEvent(&response));
2324+
BMQTST_ASSERT(response.choice().isOpenQueueResponseValue());
2325+
}
2326+
2327+
// Open a *different* queue reusing the same queueId: expect a failure
2328+
// Status rather than a crash or a silent overwrite.
2329+
tb.openQueue(uri2, queueId);
2330+
tb.d_cs.flush();
2331+
2332+
{
2333+
bmqio::TestChannel::WriteCall writeCall;
2334+
BMQTST_ASSERT(tb.d_channel->getWriteCall(&writeCall, 1));
2335+
2336+
bmqp::Event event(&writeCall.d_blob,
2337+
bmqtst::TestHelperUtil::allocator());
2338+
BMQTST_ASSERT(event.isControlEvent());
2339+
2340+
bmqp_ctrlmsg::ControlMessage response(
2341+
bmqtst::TestHelperUtil::allocator());
2342+
BMQTST_ASSERT_EQ(0, event.loadControlEvent(&response));
2343+
BMQTST_ASSERT(response.choice().isStatusValue());
2344+
BMQTST_ASSERT_EQ(response.choice().status().category(),
2345+
bmqp_ctrlmsg::StatusCategory::E_INVALID_ARGUMENT);
2346+
}
2347+
}
2348+
22652349
static void testN1_ackConfiguration()
22662350
// ------------------------------------------------------------------------
22672351
// TESTS ACK CONFIGURATION FOR CLIENT SESSION
@@ -2512,6 +2596,7 @@ int main(int argc, char* argv[])
25122596

25132597
switch (_testCase) {
25142598
case 0:
2599+
case 12: test12_openQueueDuplicateQueueId(); break;
25152600
case 11: test11_initiateShutdown(); break;
25162601
case 10: test10_newStyleCompressedPush(); break;
25172602
case 9: test9_newStylePush(); break;

src/groups/mqb/mqbblp/mqbblp_queuesessionmanager.cpp

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -282,28 +282,50 @@ void QueueSessionManager::onQueueOpenCbDispatched(
282282
BSLS_ASSERT_SAFE(confirmationCookie->d_handle);
283283
// in case of success, the cookie must be a valid shared_ptr
284284

285-
// Update the cookie to point to a null queue handle, which indicates
286-
// that requester (this client session) has successfully received and
287-
// processed the open-queue response.
288-
confirmationCookie->d_handle = 0;
289-
290-
// Success, configure the handle and the session
285+
// Configure the handle and the session
291286
bsl::pair<QueueStateMap::iterator, bool> ins = d_queues.emplace(
292287
bsl::make_pair(queueId.id(), QueueState()));
293288
QueueState& qs = ins.first->second;
294289

295290
if (ins.second) {
296-
// First time we use this queue
291+
// First time we use this queueId
297292
qs.d_handle_p = queueHandle;
298293
}
299-
else {
300-
// We've used this queue before. Search for the subId in the
301-
// associated map of substream information. If it is found, do
302-
// nothing; otherwise, insert it into the map of substream
303-
// information.
304-
BSLS_ASSERT_SAFE(queueHandle == ins.first->second.d_handle_p);
294+
else if (qs.d_handle_p != queueHandle) {
295+
// The client reused an existing queueId for a different queue
296+
// handle (i.e. a queue/URI other than the one already associated
297+
// with this queueId). This is a client protocol violation: reject
298+
// the request and roll back the handle that was just opened
299+
// upstream.
300+
BALL_LOG_ERROR
301+
<< "#CLIENT_IMPROPER_BEHAVIOR "
302+
<< d_dispatcherClient_p->description()
303+
<< ": Rejecting openQueue: queueId " << queueId
304+
<< " is already in use for a different queue [uri: '"
305+
<< handleParams.uri() << "'].";
306+
307+
bmqp_ctrlmsg::Status failure;
308+
failure.category() =
309+
bmqp_ctrlmsg::StatusCategory::E_INVALID_ARGUMENT;
310+
failure.code() = -1;
311+
failure.message() = "queueId is already in use for a different "
312+
"queue";
313+
314+
responseCallback(failure, queueHandle, openQueueResponse);
315+
return; // RETURN
305316
}
306317

318+
// At this point the queueId maps to 'queueHandle': either it was just
319+
// recorded above (first use of this queueId), or it was already
320+
// associated with the same handle (e.g. a new fan-out substream on an
321+
// already-opened queue). Confirm the response and register the
322+
// substream.
323+
324+
// Update the cookie to point to a null queue handle, which indicates
325+
// that requester (this client session) has successfully received and
326+
// processed the open-queue response.
327+
confirmationCookie->d_handle = 0;
328+
307329
QueueState::StreamsMap::iterator subQueueInfo =
308330
qs.d_subQueueInfosMap.insert(apppId, queueId.subId(), queueId);
309331

0 commit comments

Comments
 (0)