Skip to content

Commit 2d525ec

Browse files
Fix compound queue removal callback propagation
Forward REMOVED callbacks from descendant queues through CompoundPacketQueueBase so observers are notified whenever a packet leaves the enclosing logical queue, including leaf-initiated and shared-buffer removals. Track the packet currently removed at the compound boundary with a scoped save-and-restore guard. Suppress only the matching descendant callback during boundary removal and overflow victim detachment, preserving exactly-once delivery and the intended DROPPED reason while allowing nested or reentrant removals of other packets to propagate. Extend the ADDBA transaction unit coverage for direct and nested compounds, bulk and shared-buffer removal, boundary remove/dequeue/pull paths, reentrant removal, and compound capacity drops. Validation: debug build; focused Ieee80211AddbaTransaction_1 unit test; focused PriorityQueue and EthernetQosQueue fingerprints; focused architecture check; independent semantic review.
1 parent 9c48302 commit 2d525ec

3 files changed

Lines changed: 149 additions & 5 deletions

File tree

src/inet/queueing/queue/CompoundPacketQueueBase.cc

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,22 @@ namespace queueing {
1414

1515
Define_Module(CompoundPacketQueueBase);
1616

17+
class ScopedPacketRemoval
18+
{
19+
protected:
20+
Packet *&packetBeingRemoved;
21+
Packet *previousPacket;
22+
23+
public:
24+
ScopedPacketRemoval(Packet *&packetBeingRemoved, Packet *packet) :
25+
packetBeingRemoved(packetBeingRemoved), previousPacket(packetBeingRemoved)
26+
{
27+
packetBeingRemoved = packet;
28+
}
29+
30+
~ScopedPacketRemoval() { packetBeingRemoved = previousPacket; }
31+
};
32+
1733
void CompoundPacketQueueBase::initialize(int stage)
1834
{
1935
PacketQueueBase::initialize(stage);
@@ -88,7 +104,10 @@ void CompoundPacketQueueBase::pushPacket(Packet *packet, const cGate *gate)
88104
while (isOverloaded()) {
89105
auto packet = packetDropperFunction->selectPacket(this);
90106
EV_INFO << "Dropping packet" << EV_FIELD(packet) << EV_ENDL;
91-
collection->removePacket(packet);
107+
{
108+
ScopedPacketRemoval scopedPacketRemoval(packetBeingRemoved, packet);
109+
collection->removePacket(packet);
110+
}
92111
emit(packetRemovedSignal, packet);
93112
droppedPackets.push_back(packet);
94113
}
@@ -115,7 +134,10 @@ Packet *CompoundPacketQueueBase::pullPacket(const cGate *gate)
115134
void CompoundPacketQueueBase::removePacket(Packet *packet)
116135
{
117136
Enter_Method("removePacket");
118-
collection->removePacket(packet);
137+
{
138+
ScopedPacketRemoval scopedPacketRemoval(packetBeingRemoved, packet);
139+
collection->removePacket(packet);
140+
}
119141
notifyPacketRemoved(packet, IPacketQueue::PacketRemovalReason::REMOVED);
120142
emit(packetRemovedSignal, packet);
121143
}
@@ -187,7 +209,8 @@ void CompoundPacketQueueBase::receiveSignal(cComponent *source, simsignal_t sign
187209
void CompoundPacketQueueBase::handlePacketRemoved(Packet *packet, IPacketQueue::PacketRemovalReason reason)
188210
{
189211
Enter_Method("handlePacketRemoved");
190-
if (reason == IPacketQueue::PacketRemovalReason::DROPPED)
212+
if (reason == IPacketQueue::PacketRemovalReason::DROPPED ||
213+
(reason == IPacketQueue::PacketRemovalReason::REMOVED && packet != packetBeingRemoved))
191214
notifyPacketRemoved(packet, reason);
192215
}
193216

src/inet/queueing/queue/CompoundPacketQueueBase.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class INET_API CompoundPacketQueueBase : public PacketQueueBase, public cListene
2828
IPacketCollection *collection = nullptr;
2929
IPacketExtractor *packetExtractor = nullptr;
3030
std::vector<IPacketQueue *> childQueues;
31+
Packet *packetBeingRemoved = nullptr;
3132

3233
IPacketDropperFunction *packetDropperFunction = nullptr;
3334

tests/unit/Ieee80211AddbaTransaction_1.test

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
360376
class 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

Comments
 (0)