@@ -357,6 +357,22 @@ class TestPacketDropCallback : public queueing::IPacketQueue::ICallback
357357 }
358358};
359359
360+ class TestReentrantRemovalCallback : public queueing::IPacketQueue::ICallback
361+ {
362+ public:
363+ queueing::IPacketQueue *queue = nullptr;
364+ Packet *triggerPacket = nullptr;
365+ Packet *packetToRemove = nullptr;
366+ bool removedPacket = false;
367+
368+ virtual void handlePacketRemoved(Packet *packet, queueing::IPacketQueue::PacketRemovalReason reason) override {
369+ if (!removedPacket && packet == triggerPacket && reason == queueing::IPacketQueue::PacketRemovalReason::REMOVED) {
370+ removedPacket = true;
371+ queue->removePacket(packetToRemove);
372+ }
373+ }
374+ };
375+
360376class TestPacketPulledListener : public cListener
361377{
362378 public:
@@ -3349,6 +3365,10 @@ delete compoundPacket;
33493365 auto secondQueue = createBufferedQueue("sharedRemovalSecondQueue");
33503366 TestPacketDropCallback firstCallback;
33513367 TestPacketDropCallback secondCallback;
3368+ TestPacketDropCallback compoundCallback;
3369+ TestCompoundPacketQueue bufferedCompound;
3370+ bufferedCompound.observeQueue(secondQueue);
3371+ bufferedCompound.addObserver(&compoundCallback);
33523372 firstQueue->addPacketCallback(&firstCallback);
33533373 secondQueue->addPacketCallback(&secondCallback);
33543374 firstQueue->pushPacket(new Packet("sharedRemovalFirstA", makeShared<ByteCountChunk>(B(1))), nullptr);
@@ -3367,8 +3387,11 @@ delete compoundPacket;
33673387 ASSERT(buffer->getNumPackets() == 0);
33683388 ASSERT(secondCallback.numRemovedPackets == 1);
33693389 ASSERT(secondCallback.numDroppedPackets == 0);
3390+ ASSERT(compoundCallback.numRemovedPackets == 1);
3391+ ASSERT(compoundCallback.numDroppedPackets == 0);
33703392 take(retainedPacket);
33713393 delete retainedPacket;
3394+ bufferedCompound.stopObserving();
33723395 firstQueue->removePacketCallback(&firstCallback);
33733396 secondQueue->removePacketCallback(&secondCallback);
33743397 firstQueue->callFinish();
@@ -3426,8 +3449,10 @@ delete compoundPacket;
34263449 overflowQueue->deleteModule();
34273450}
34283451
3429- // A compound's direct frontier and a nested compound boundary each forward a
3430- // destructive removal exactly once.
3452+ // A compound's direct frontier and a nested compound boundary each forward
3453+ // descendant removals exactly once. Boundary-initiated removals suppress the
3454+ // descendant callback for the same packet, including during reentrant removal
3455+ // of a different packet.
34313456{
34323457 auto pendingQueueModule = cModuleType::get("inet.linklayer.ieee80211.mac.queue.CompoundPendingQueue")->create("callbackPendingQueue", this);
34333458 pendingQueueModule->callInitialize();
@@ -3439,11 +3464,78 @@ delete compoundPacket;
34393464 TestCompoundPacketQueue outerCompound;
34403465 outerCompound.observeQueue(pendingQueue);
34413466 outerCompound.addObserver(&nestedCallback);
3467+
34423468 auto directPacket = new Packet("directCompoundDrop", makeShared<ByteCountChunk>(B(1)));
34433469 directLeaf->handlePacketDropped(directPacket);
34443470 ASSERT(directCallback.numDroppedPackets == 1);
34453471 ASSERT(nestedCallback.numDroppedPackets == 1);
34463472 delete directPacket;
3473+
3474+ auto leafRemovedPacket = new Packet("leafRemovedPacket", makeShared<ByteCountChunk>(B(1)));
3475+ directLeaf->pushPacket(leafRemovedPacket, nullptr);
3476+ directLeaf->removePacket(leafRemovedPacket);
3477+ ASSERT(directCallback.numRemovedPackets == 1);
3478+ ASSERT(nestedCallback.numRemovedPackets == 1);
3479+ take(leafRemovedPacket);
3480+ delete leafRemovedPacket;
3481+
3482+ directLeaf->pushPacket(new Packet("leafBulkRemovedFirst", makeShared<ByteCountChunk>(B(1))), nullptr);
3483+ directLeaf->pushPacket(new Packet("leafBulkRemovedSecond", makeShared<ByteCountChunk>(B(1))), nullptr);
3484+ directLeaf->removeAllPackets();
3485+ ASSERT(directCallback.numRemovedPackets == 3);
3486+ ASSERT(nestedCallback.numRemovedPackets == 3);
3487+
3488+ auto boundaryRemovedPacket = new Packet("boundaryRemovedPacket", makeShared<ByteCountChunk>(B(1)));
3489+ directLeaf->pushPacket(boundaryRemovedPacket, nullptr);
3490+ pendingQueue->removePacket(boundaryRemovedPacket);
3491+ ASSERT(directCallback.numRemovedPackets == 4);
3492+ ASSERT(nestedCallback.numRemovedPackets == 4);
3493+ take(boundaryRemovedPacket);
3494+ delete boundaryRemovedPacket;
3495+
3496+ directLeaf->pushPacket(new Packet("boundaryBulkRemovedFirst", makeShared<ByteCountChunk>(B(1))), nullptr);
3497+ directLeaf->pushPacket(new Packet("boundaryBulkRemovedSecond", makeShared<ByteCountChunk>(B(1))), nullptr);
3498+ pendingQueue->removeAllPackets();
3499+ ASSERT(directCallback.numRemovedPackets == 6);
3500+ ASSERT(nestedCallback.numRemovedPackets == 6);
3501+
3502+ auto boundaryDequeuedPacket = new Packet("boundaryDequeuedPacket", makeShared<ByteCountChunk>(B(1)));
3503+ directLeaf->pushPacket(boundaryDequeuedPacket, nullptr);
3504+ auto dequeuedPacket = pendingQueue->dequeuePacket([boundaryDequeuedPacket](const Packet *packet) { return packet == boundaryDequeuedPacket; });
3505+ ASSERT(dequeuedPacket == boundaryDequeuedPacket);
3506+ ASSERT(directCallback.numDequeuedPackets == 1);
3507+ ASSERT(nestedCallback.numDequeuedPackets == 0);
3508+ take(dequeuedPacket);
3509+ delete dequeuedPacket;
3510+
3511+ auto boundaryPulledPacket = new Packet("boundaryPulledPacket", makeShared<ByteCountChunk>(B(1)));
3512+ directLeaf->pushPacket(boundaryPulledPacket, nullptr);
3513+ auto pulledPacket = pendingQueue->pullPacket(nullptr);
3514+ ASSERT(pulledPacket == boundaryPulledPacket);
3515+ ASSERT(directCallback.numDequeuedPackets == 2);
3516+ ASSERT(nestedCallback.numDequeuedPackets == 0);
3517+ take(pulledPacket);
3518+ delete pulledPacket;
3519+
3520+ auto reentrantTriggerPacket = new Packet("reentrantTriggerPacket", makeShared<ByteCountChunk>(B(1)));
3521+ auto reentrantRemovedPacket = new Packet("reentrantRemovedPacket", makeShared<ByteCountChunk>(B(1)));
3522+ directLeaf->pushPacket(reentrantTriggerPacket, nullptr);
3523+ directLeaf->pushPacket(reentrantRemovedPacket, nullptr);
3524+ TestReentrantRemovalCallback reentrantCallback;
3525+ reentrantCallback.queue = directLeaf;
3526+ reentrantCallback.triggerPacket = reentrantTriggerPacket;
3527+ reentrantCallback.packetToRemove = reentrantRemovedPacket;
3528+ directLeaf->addPacketCallback(&reentrantCallback);
3529+ pendingQueue->removePacket(reentrantTriggerPacket);
3530+ ASSERT(reentrantCallback.removedPacket);
3531+ ASSERT(directCallback.numRemovedPackets == 8);
3532+ ASSERT(nestedCallback.numRemovedPackets == 8);
3533+ directLeaf->removePacketCallback(&reentrantCallback);
3534+ take(reentrantTriggerPacket);
3535+ take(reentrantRemovedPacket);
3536+ delete reentrantTriggerPacket;
3537+ delete reentrantRemovedPacket;
3538+
34473539 outerCompound.stopObserving();
34483540 pendingQueue->removePacketCallback(&directCallback);
34493541 pendingQueueModule->callFinish();
@@ -3467,6 +3559,34 @@ delete compoundPacket;
34673559 packetShaperModule->deleteModule();
34683560}
34693561
3562+ // Compound capacity enforcement detaches its leaf victim without leaking a
3563+ // REMOVED callback before reporting the single destructive departure.
3564+ {
3565+ auto pendingQueueModule = cModuleType::get("inet.linklayer.ieee80211.mac.queue.CompoundPendingQueue")->create("callbackOverflowPendingQueue", this);
3566+ pendingQueueModule->par("packetCapacity").setIntValue(1);
3567+ pendingQueueModule->par("dropperClass").setStringValue("inet::queueing::PacketAtCollectionEndDropper");
3568+ pendingQueueModule->callInitialize();
3569+ auto pendingQueue = check_and_cast<queueing::IPacketQueue *>(pendingQueueModule);
3570+ TestPacketDropCallback directCallback;
3571+ TestPacketDropCallback nestedCallback;
3572+ pendingQueue->addPacketCallback(&directCallback);
3573+ TestCompoundPacketQueue outerCompound;
3574+ outerCompound.observeQueue(pendingQueue);
3575+ outerCompound.addObserver(&nestedCallback);
3576+ pendingQueue->pushPacket(new Packet("compoundRetainedPacket", makeQosHeader(peer1, 1, SequenceNumberCyclic(1))), nullptr);
3577+ pendingQueue->pushPacket(new Packet("compoundOverflowPacket", makeQosHeader(peer1, 1, SequenceNumberCyclic(2))), nullptr);
3578+ ASSERT(pendingQueue->getNumPackets() == 1);
3579+ ASSERT(directCallback.numDroppedPackets == 1);
3580+ ASSERT(directCallback.numRemovedPackets == 0);
3581+ ASSERT(nestedCallback.numDroppedPackets == 1);
3582+ ASSERT(nestedCallback.numRemovedPackets == 0);
3583+ outerCompound.stopObserving();
3584+ pendingQueue->removePacketCallback(&directCallback);
3585+ pendingQueue->removeAllPackets();
3586+ pendingQueueModule->callFinish();
3587+ pendingQueueModule->deleteModule();
3588+ }
3589+
34703590// A shared buffer detaches every victim from its owning queue before the first
34713591// typed drop callback. This keeps protocol cleanup from reentering while a
34723592// later victim is still selectable from another queue.
0 commit comments