| /* |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.qpid.server.queue; |
| |
| |
| import org.apache.qpid.server.consumer.ConsumerImpl; |
| import org.apache.qpid.server.message.MessageInstance; |
| import org.apache.qpid.test.utils.QpidTestCase; |
| |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.when; |
| |
| public class ConsumerListTest extends QpidTestCase |
| { |
| private QueueConsumerList _subList; |
| private QueueConsumer _sub1; |
| private QueueConsumer _sub2; |
| private QueueConsumer _sub3; |
| private ConsumerNode _node; |
| |
| protected void setUp() |
| { |
| _subList = new QueueConsumerList(); |
| |
| _sub1 = newMockConsumer(); |
| _sub2 = newMockConsumer(); |
| _sub3 = newMockConsumer(); |
| |
| _subList.add(_sub1); |
| _subList.add(_sub2); |
| _subList.add(_sub3); |
| |
| _node = _subList.getHead(); |
| } |
| |
| |
| private QueueConsumer newMockConsumer() |
| { |
| QueueConsumer consumer = mock(QueueConsumer.class); |
| MessageInstance.StealableConsumerAcquiredState |
| owningState = new MessageInstance.StealableConsumerAcquiredState(consumer); |
| when(consumer.getOwningState()).thenReturn(owningState); |
| |
| return consumer; |
| } |
| |
| /** |
| * Test that if the first (non-head) node in the list is deleted (but is still present), |
| * it is not returned when searching through the list for the next viable node, and the |
| * subsequent viable node is returned instead. |
| */ |
| public void testFindNextSkipsFirstDeletedNode() |
| { |
| assertTrue("Deleting consumer node should have succeeded", |
| getNodeForConsumer(_subList, _sub1).delete()); |
| |
| assertNotNull("Returned node should not be null", _node = _node.findNext()); |
| assertEquals("Should have returned node for 2nd consumer", _sub2, _node.getConsumer()); |
| |
| assertNotNull("Returned node should not be null", _node = _node.findNext()); |
| assertEquals("Should have returned node for 3rd consumer", _sub3, _node.getConsumer()); |
| } |
| |
| /** |
| * Test that if a central node in the list is deleted (but is still present), |
| * it is not returned when searching through the list for the next viable node, |
| * and the subsequent viable node is returned instead. |
| */ |
| public void testFindNextSkipsCentralDeletedNode() |
| { |
| assertNotNull("Returned node should not be null", _node = _node.findNext()); |
| |
| assertTrue("Deleting consumer node should have succeeded", |
| getNodeForConsumer(_subList, _sub2).delete()); |
| |
| assertNotNull("Returned node should not be null", _node = _node.findNext()); |
| assertEquals("Should have returned node for 3rd consumer", _sub3, _node.getConsumer()); |
| } |
| |
| /** |
| * Test that if the last node in the list is deleted (but is still present), |
| * it is not returned when searching through the list for the next viable node, |
| * and null is returned instead. |
| */ |
| public void testFindNextSkipsLastDeletedNode() |
| { |
| assertNotNull("Returned node should not be null", _node = _node.findNext()); |
| assertEquals("Should have returned node for 1st consumer", _sub1, _node.getConsumer()); |
| |
| assertNotNull("Returned node should not be null", _node = _node.findNext()); |
| assertEquals("Should have returned node for 2nd consumer", _sub2, _node.getConsumer()); |
| |
| assertTrue("Deleting consumer node should have succeeded", |
| getNodeForConsumer(_subList, _sub3).delete()); |
| |
| assertNull("Returned node should be null", _node = _node.findNext()); |
| } |
| |
| /** |
| * Test that if multiple nodes in the list are deleted (but still present), they |
| * are not returned when searching through the list for the next viable node, |
| * and the subsequent viable node is returned instead. |
| */ |
| public void testFindNextSkipsMultipleDeletedNode() |
| { |
| assertTrue("Deleting consumer node should have succeeded", |
| getNodeForConsumer(_subList, _sub1).delete()); |
| assertTrue("Deleting consumer node should have succeeded", |
| getNodeForConsumer(_subList, _sub2).delete()); |
| |
| assertNotNull("Returned node should not be null", _node = _node.findNext()); |
| assertEquals("Should have returned node for 3rd consumer", _sub3, _node.getConsumer()); |
| } |
| |
| /** |
| * Test that if a node in the list is marked 'deleted' it is still present in the list |
| * until actually removed. counter-test to verify above testing of getNext() method. |
| */ |
| public void testDeletedNodeStillPresent() |
| { |
| assertTrue("Deleting consumer node should have succeeded", |
| getNodeForConsumer(_subList, _sub1).delete()); |
| |
| assertNotNull("Node marked deleted should still be present", getNodeForConsumer(_subList, _sub1)); |
| assertEquals("All 3 nodes are still expected to be present", 3, countNodes(_subList)); |
| } |
| |
| /** |
| * Traverses the list nodes in a non-mutating fashion, returning the first node which matches the given |
| * Consumer, or null if none is found. |
| */ |
| private ConsumerNode getNodeForConsumer(final QueueConsumerList list, final ConsumerImpl sub) |
| { |
| ConsumerNode node = list.getHead(); |
| while (node != null && node.getConsumer() != sub) |
| { |
| node = node.nextNode(); |
| } |
| |
| return node; |
| } |
| |
| /** |
| * Counts the number of (non-head) nodes in the list. |
| */ |
| private int countNodes(final QueueConsumerList list) |
| { |
| ConsumerNode node = list.getHead(); |
| int count; |
| for(count = -1; node != null; count++) |
| { |
| node = node.nextNode(); |
| } |
| |
| return count; |
| } |
| |
| /** |
| * Tests that the head is returned as expected, and isn't the node for the first consumer. |
| */ |
| public void testGetHead() |
| { |
| assertNotNull("List head should be non null", _node); |
| assertNotSame("Head should not be node for first consumer", |
| _node, getNodeForConsumer(_subList, _sub1)); |
| } |
| |
| /** |
| * Tests that the size is returned correctly in the face of additions and removals. |
| */ |
| public void testGetSize() |
| { |
| QueueConsumerList subList = new QueueConsumerList(); |
| |
| assertEquals("Unexpected size result", 0, subList.size()); |
| |
| QueueConsumer sub1 = newMockConsumer(); |
| QueueConsumer sub2 = newMockConsumer(); |
| QueueConsumer sub3 = newMockConsumer(); |
| |
| subList.add(sub1); |
| assertEquals("Unexpected size result", 1, subList.size()); |
| |
| subList.add(sub2); |
| assertEquals("Unexpected size result", 2, subList.size()); |
| |
| subList.add(sub3); |
| assertEquals("Unexpected size result", 3, subList.size()); |
| |
| assertTrue("Removing consumer from list should have succeeded", subList.remove(sub1)); |
| assertEquals("Unexpected size result", 2, subList.size()); |
| |
| assertTrue("Removing consumer from list should have succeeded", subList.remove(sub2)); |
| assertEquals("Unexpected size result", 1, subList.size()); |
| |
| assertTrue("Removing consumer from list should have succeeded", subList.remove(sub3)); |
| assertEquals("Unexpected size result", 0, subList.size()); |
| } |
| |
| /** |
| * Test that if the first (non-head) node in the list is removed it is no longer |
| * present in the node structure of the list at all. |
| */ |
| public void testRemoveFirstNode() |
| { |
| assertNotNull("Should have been a node present for the consumer", getNodeForConsumer(_subList, _sub1)); |
| assertTrue("Removing consumer node should have succeeded", _subList.remove(_sub1)); |
| assertNull("Should not have been a node present for the removed consumer", |
| getNodeForConsumer(_subList, _sub1)); |
| assertEquals("Unexpected number of nodes", 2, countNodes(_subList)); |
| assertNotNull("Should have been a node present for the consumer", getNodeForConsumer(_subList, _sub2)); |
| assertNotNull("Should have been a node present for the consumer", getNodeForConsumer(_subList, _sub3)); |
| } |
| |
| /** |
| * Test that if a central node in the list is removed it is no longer |
| * present in the node structure of the list at all. |
| */ |
| public void testRemoveCentralNode() |
| { |
| assertNotNull("Should have been a node present for the consumer", getNodeForConsumer(_subList, _sub2)); |
| assertTrue("Removing consumer node should have succeeded", _subList.remove(_sub2)); |
| assertNull("Should not have been a node present for the removed consumer", |
| getNodeForConsumer(_subList, _sub2)); |
| assertEquals("Unexpected number of nodes", 2, countNodes(_subList)); |
| assertNotNull("Should have been a node present for the consumer", getNodeForConsumer(_subList, _sub1)); |
| assertNotNull("Should have been a node present for the consumer", getNodeForConsumer(_subList, _sub3)); |
| } |
| |
| /** |
| * Test that if the consumer contained in the last node of the list is removed |
| * it is no longer present in the node structure of the list at all. However, |
| * as the last node in the structure can't actually be removed a dummy will instead |
| * be present. |
| */ |
| public void testRemoveLastNode() |
| { |
| assertNotNull("Should have been a node present for the consumer", getNodeForConsumer(_subList, _sub3)); |
| assertTrue("Removing consumer node should have succeeded", _subList.remove(_sub3)); |
| assertNull("Should not have been a node present for the removed consumer", |
| getNodeForConsumer(_subList, _sub3)); |
| |
| //We actually expect 3 nodes to remain this time, because the last node cant be removed for thread safety reasons, |
| //however a dummy final node can be used as substitute to allow removal of the consumer node. |
| assertEquals("Unexpected number of nodes", 2 + 1, countNodes(_subList)); |
| assertNotNull("Should have been a node present for the consumer", getNodeForConsumer(_subList, _sub1)); |
| assertNotNull("Should have been a node present for the consumer", getNodeForConsumer(_subList, _sub2)); |
| } |
| |
| /** |
| * Test that if the consumer not contained in the list is requested to be removed |
| * that the removal fails |
| */ |
| public void testRemoveNonexistentNode() |
| { |
| QueueConsumer sub4 = newMockConsumer(); |
| assertNull("Should not have been a node present for the consumer", getNodeForConsumer(_subList, sub4)); |
| assertFalse("Removing consumer node should not have succeeded", _subList.remove(sub4)); |
| assertEquals("Unexpected number of nodes", 3, countNodes(_subList)); |
| } |
| |
| /** |
| * Test that if a consumer node which occurs later in the main list than the marked node is |
| * removed from the list after the marked node is also removed, then the marker node doesn't |
| * serve to retain the subsequent nodes in the list structure (and thus memory) despite their |
| * removal. |
| */ |
| public void testDeletedMarkedNodeDoesntLeakSubsequentlyDeletedNodes() |
| { |
| //get the nodes out the list for the 1st and 3rd consumers |
| ConsumerNode sub1Node = getNodeForConsumer(_subList, _sub1); |
| assertNotNull("Should have been a node present for the consumer", sub1Node); |
| ConsumerNode sub3Node = getNodeForConsumer(_subList, _sub3); |
| assertNotNull("Should have been a node present for the consumer", sub3Node); |
| |
| //mark the first consumer node |
| assertTrue("should have succeeded in updating the marked node", |
| _subList.updateMarkedNode(_subList.getMarkedNode(), sub1Node)); |
| |
| //remove the 1st consumer from the list |
| assertTrue("Removing consumer node should have succeeded", _subList.remove(_sub1)); |
| //verify the 1st consumer is no longer the marker node (replaced by a dummy), or in the main list structure |
| assertNotSame("Unexpected marker node", sub1Node, _subList.getMarkedNode()); |
| assertNull("Should not have been a node present in the list structure for the marked-but-removed sub1 node", |
| getNodeForConsumer(_subList, _sub1)); |
| |
| //remove the 2nd consumer from the list |
| assertTrue("Removing consumer node should have succeeded", _subList.remove(_sub2)); |
| |
| //verify the marker node isn't leaking subsequently removed nodes, by ensuring the very next node |
| //in its list structure is now the 3rd consumer (since the 2nd was removed too) |
| assertEquals("Unexpected next node", sub3Node, _subList.getMarkedNode().nextNode()); |
| |
| //remove the 3rd and final/tail consumer |
| assertTrue("Removing consumer node should have succeeded", _subList.remove(_sub3)); |
| |
| //verify the marker node isn't leaking subsequently removed nodes, by ensuring the very next node |
| //in its list structure is now the dummy tail (since the 3rd consumer was removed, and a dummy |
| //tail was inserted) and NOT the 3rd sub node. |
| assertNotSame("Unexpected next node", sub3Node, _subList.getMarkedNode().nextNode()); |
| assertTrue("Unexpected next node", _subList.getMarkedNode().nextNode().isDeleted()); |
| assertNull("Next non-deleted node from the marker should now be the list end, i.e. null", _subList.getMarkedNode().findNext()); |
| } |
| |
| /** |
| * Test that the marked node 'findNext' behaviour is as expected after a consumer is added |
| * to the list following the tail consumer node being removed while it is the marked node. |
| * That is, that the new consumers node is returned by getMarkedNode().findNext(). |
| */ |
| public void testMarkedNodeFindsNewConsumerAfterRemovingTailWhilstMarked() |
| { |
| //get the node out the list for the 3rd consumer |
| ConsumerNode sub3Node = getNodeForConsumer(_subList, _sub3); |
| assertNotNull("Should have been a node present for the consumer", sub3Node); |
| |
| //mark the 3rd consumer node |
| assertTrue("should have succeeded in updating the marked node", |
| _subList.updateMarkedNode(_subList.getMarkedNode(), sub3Node)); |
| |
| //verify calling findNext on the marked node returns null, i.e. the end of the list has been reached |
| assertEquals("Unexpected node after marked node", null, _subList.getMarkedNode().findNext()); |
| |
| //remove the 3rd(marked) consumer from the list |
| assertTrue("Removing consumer node should have succeeded", _subList.remove(_sub3)); |
| |
| //add a new 4th consumer to the list |
| QueueConsumer sub4 = newMockConsumer(); |
| _subList.add(sub4); |
| |
| //get the node out the list for the 4th consumer |
| ConsumerNode sub4Node = getNodeForConsumer(_subList, sub4); |
| assertNotNull("Should have been a node present for the consumer", sub4Node); |
| |
| //verify the marked node (which is now a dummy substitute for the 3rd consumer) returns |
| //the 4th consumers node as the next non-deleted node. |
| assertEquals("Unexpected next node", sub4Node, _subList.getMarkedNode().findNext()); |
| } |
| |
| /** |
| * Test that setting the marked node to null doesn't cause problems during remove operations |
| */ |
| public void testRemoveWithNullMarkedNode() |
| { |
| //set the marker to null |
| assertTrue("should have succeeded in updating the marked node", |
| _subList.updateMarkedNode(_subList.getMarkedNode(), null)); |
| |
| //remove the 1st consumer from the main list |
| assertTrue("Removing consumer node should have succeeded", _subList.remove(_sub1)); |
| |
| //verify the 1st consumer is no longer in the main list structure |
| assertNull("Should not have been a node present in the main list structure for sub1", |
| getNodeForConsumer(_subList, _sub1)); |
| assertEquals("Unexpected number of nodes", 2, countNodes(_subList)); |
| } |
| |
| /** |
| * Tests that after the first (non-head) node of the list is marked deleted but has not |
| * yet been removed, the iterator still skips it. |
| */ |
| public void testIteratorSkipsFirstDeletedNode() |
| { |
| //'delete' but don't remove the node for the 1st consumer |
| assertTrue("Deleting consumer node should have succeeded", |
| getNodeForConsumer(_subList, _sub1).delete()); |
| assertNotNull("Should still have been a node present for the deleted consumer", |
| getNodeForConsumer(_subList, _sub1)); |
| |
| ConsumerNodeIterator iter = _subList.iterator(); |
| |
| //verify the iterator returns the 2nd consumers node |
| assertTrue("Iterator should have been able to advance", iter.advance()); |
| assertEquals("Iterator returned unexpected ConsumerNode", _sub2, iter.getNode().getConsumer()); |
| |
| //verify the iterator returns the 3rd consumers node and not the 2nd. |
| assertTrue("Iterator should have been able to advance", iter.advance()); |
| assertEquals("Iterator returned unexpected ConsumerNode", _sub3, iter.getNode().getConsumer()); |
| } |
| |
| /** |
| * Tests that after a central node of the list is marked deleted but has not yet been removed, |
| * the iterator still skips it. |
| */ |
| public void testIteratorSkipsCentralDeletedNode() |
| { |
| //'delete' but don't remove the node for the 2nd consumer |
| assertTrue("Deleting consumer node should have succeeded", |
| getNodeForConsumer(_subList, _sub2).delete()); |
| assertNotNull("Should still have been a node present for the deleted consumer", |
| getNodeForConsumer(_subList, _sub2)); |
| |
| ConsumerNodeIterator iter = _subList.iterator(); |
| |
| //verify the iterator returns the 1st consumers node |
| assertTrue("Iterator should have been able to advance", iter.advance()); |
| assertEquals("Iterator returned unexpected ConsumerNode", _sub1, iter.getNode().getConsumer()); |
| |
| //verify the iterator returns the 3rd consumers node and not the 2nd. |
| assertTrue("Iterator should have been able to advance", iter.advance()); |
| assertEquals("Iterator returned unexpected ConsumerNode", _sub3, iter.getNode().getConsumer()); |
| } |
| |
| /** |
| * Tests that after the last node of the list is marked deleted but has not yet been removed, |
| * the iterator still skips it. |
| */ |
| public void testIteratorSkipsDeletedFinalNode() |
| { |
| //'delete' but don't remove the node for the 3rd consumer |
| assertTrue("Deleting consumer node should have succeeded", |
| getNodeForConsumer(_subList, _sub3).delete()); |
| assertNotNull("Should still have been a node present for the deleted 3rd consumer", |
| getNodeForConsumer(_subList, _sub3)); |
| |
| ConsumerNodeIterator iter = _subList.iterator(); |
| |
| //verify the iterator returns the 1st consumers node |
| assertTrue("Iterator should have been able to advance", iter.advance()); |
| assertEquals("Iterator returned unexpected ConsumerNode", _sub1, iter.getNode().getConsumer()); |
| |
| //verify the iterator returns the 2nd consumers node |
| assertTrue("Iterator should have been able to advance", iter.advance()); |
| assertEquals("Iterator returned unexpected ConsumerNode", _sub2, iter.getNode().getConsumer()); |
| |
| //verify the iterator can no longer advance and does not return a consumer node |
| assertFalse("Iterator should not have been able to advance", iter.advance()); |
| assertEquals("Iterator returned unexpected ConsumerNode", null, iter.getNode()); |
| } |
| } |