|
@@ -0,0 +1,392 @@
|
|
|
+/**
|
|
|
+ * 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.hadoop.ipc;
|
|
|
+
|
|
|
+import static org.junit.Assert.assertEquals;
|
|
|
+import static org.junit.Assert.assertFalse;
|
|
|
+import static org.junit.Assert.assertNotNull;
|
|
|
+import static org.junit.Assert.assertNull;
|
|
|
+import static org.junit.Assert.assertTrue;
|
|
|
+
|
|
|
+import static org.mockito.Mockito.mock;
|
|
|
+import static org.mockito.Mockito.when;
|
|
|
+
|
|
|
+import junit.framework.TestCase;
|
|
|
+
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+import java.util.concurrent.BlockingQueue;
|
|
|
+
|
|
|
+import org.apache.hadoop.security.UserGroupInformation;
|
|
|
+import org.apache.hadoop.conf.Configuration;
|
|
|
+import org.mockito.Matchers;
|
|
|
+
|
|
|
+import static org.apache.hadoop.ipc.FairCallQueue.IPC_CALLQUEUE_PRIORITY_LEVELS_KEY;
|
|
|
+
|
|
|
+public class TestFairCallQueue extends TestCase {
|
|
|
+ private FairCallQueue<Schedulable> fcq;
|
|
|
+
|
|
|
+ private Schedulable mockCall(String id) {
|
|
|
+ Schedulable mockCall = mock(Schedulable.class);
|
|
|
+ UserGroupInformation ugi = mock(UserGroupInformation.class);
|
|
|
+
|
|
|
+ when(ugi.getUserName()).thenReturn(id);
|
|
|
+ when(mockCall.getUserGroupInformation()).thenReturn(ugi);
|
|
|
+
|
|
|
+ return mockCall;
|
|
|
+ }
|
|
|
+
|
|
|
+ // A scheduler which always schedules into priority zero
|
|
|
+ private RpcScheduler alwaysZeroScheduler;
|
|
|
+ {
|
|
|
+ RpcScheduler sched = mock(RpcScheduler.class);
|
|
|
+ when(sched.getPriorityLevel(Matchers.<Schedulable>any())).thenReturn(0); // always queue 0
|
|
|
+ alwaysZeroScheduler = sched;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setUp() {
|
|
|
+ Configuration conf = new Configuration();
|
|
|
+ conf.setInt("ns." + IPC_CALLQUEUE_PRIORITY_LEVELS_KEY, 2);
|
|
|
+
|
|
|
+ fcq = new FairCallQueue<Schedulable>(5, "ns", conf);
|
|
|
+ }
|
|
|
+
|
|
|
+ //
|
|
|
+ // Ensure that FairCallQueue properly implements BlockingQueue
|
|
|
+ //
|
|
|
+ public void testPollReturnsNullWhenEmpty() {
|
|
|
+ assertNull(fcq.poll());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testPollReturnsTopCallWhenNotEmpty() {
|
|
|
+ Schedulable call = mockCall("c");
|
|
|
+ assertTrue(fcq.offer(call));
|
|
|
+
|
|
|
+ assertEquals(call, fcq.poll());
|
|
|
+
|
|
|
+ // Poll took it out so the fcq is empty
|
|
|
+ assertEquals(0, fcq.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testOfferSucceeds() {
|
|
|
+ fcq.setScheduler(alwaysZeroScheduler);
|
|
|
+
|
|
|
+ for (int i = 0; i < 5; i++) {
|
|
|
+ // We can fit 10 calls
|
|
|
+ assertTrue(fcq.offer(mockCall("c")));
|
|
|
+ }
|
|
|
+
|
|
|
+ assertEquals(5, fcq.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testOfferFailsWhenFull() {
|
|
|
+ fcq.setScheduler(alwaysZeroScheduler);
|
|
|
+ for (int i = 0; i < 5; i++) { assertTrue(fcq.offer(mockCall("c"))); }
|
|
|
+
|
|
|
+ assertFalse(fcq.offer(mockCall("c"))); // It's full
|
|
|
+
|
|
|
+ assertEquals(5, fcq.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testOfferSucceedsWhenScheduledLowPriority() {
|
|
|
+ // Scheduler will schedule into queue 0 x 5, then queue 1
|
|
|
+ RpcScheduler sched = mock(RpcScheduler.class);
|
|
|
+ when(sched.getPriorityLevel(Matchers.<Schedulable>any())).thenReturn(0, 0, 0, 0, 0, 1, 0);
|
|
|
+ fcq.setScheduler(sched);
|
|
|
+ for (int i = 0; i < 5; i++) { assertTrue(fcq.offer(mockCall("c"))); }
|
|
|
+
|
|
|
+ assertTrue(fcq.offer(mockCall("c")));
|
|
|
+
|
|
|
+ assertEquals(6, fcq.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testPeekNullWhenEmpty() {
|
|
|
+ assertNull(fcq.peek());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testPeekNonDestructive() {
|
|
|
+ Schedulable call = mockCall("c");
|
|
|
+ assertTrue(fcq.offer(call));
|
|
|
+
|
|
|
+ assertEquals(call, fcq.peek());
|
|
|
+ assertEquals(call, fcq.peek()); // Non-destructive
|
|
|
+ assertEquals(1, fcq.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testPeekPointsAtHead() {
|
|
|
+ Schedulable call = mockCall("c");
|
|
|
+ Schedulable next = mockCall("b");
|
|
|
+ fcq.offer(call);
|
|
|
+ fcq.offer(next);
|
|
|
+
|
|
|
+ assertEquals(call, fcq.peek()); // Peek points at the head
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testPollTimeout() throws InterruptedException {
|
|
|
+ fcq.setScheduler(alwaysZeroScheduler);
|
|
|
+
|
|
|
+ assertNull(fcq.poll(10, TimeUnit.MILLISECONDS));
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testPollSuccess() throws InterruptedException {
|
|
|
+ fcq.setScheduler(alwaysZeroScheduler);
|
|
|
+
|
|
|
+ Schedulable call = mockCall("c");
|
|
|
+ assertTrue(fcq.offer(call));
|
|
|
+
|
|
|
+ assertEquals(call, fcq.poll(10, TimeUnit.MILLISECONDS));
|
|
|
+
|
|
|
+ assertEquals(0, fcq.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testOfferTimeout() throws InterruptedException {
|
|
|
+ fcq.setScheduler(alwaysZeroScheduler);
|
|
|
+ for (int i = 0; i < 5; i++) {
|
|
|
+ assertTrue(fcq.offer(mockCall("c"), 10, TimeUnit.MILLISECONDS));
|
|
|
+ }
|
|
|
+
|
|
|
+ assertFalse(fcq.offer(mockCall("e"), 10, TimeUnit.MILLISECONDS)); // It's full
|
|
|
+
|
|
|
+ assertEquals(5, fcq.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testDrainTo() {
|
|
|
+ Configuration conf = new Configuration();
|
|
|
+ conf.setInt("ns." + IPC_CALLQUEUE_PRIORITY_LEVELS_KEY, 2);
|
|
|
+ FairCallQueue<Schedulable> fcq2 = new FairCallQueue<Schedulable>(10, "ns", conf);
|
|
|
+
|
|
|
+ fcq.setScheduler(alwaysZeroScheduler);
|
|
|
+ fcq2.setScheduler(alwaysZeroScheduler);
|
|
|
+
|
|
|
+ // Start with 3 in fcq, to be drained
|
|
|
+ for (int i = 0; i < 3; i++) {
|
|
|
+ fcq.offer(mockCall("c"));
|
|
|
+ }
|
|
|
+
|
|
|
+ fcq.drainTo(fcq2);
|
|
|
+
|
|
|
+ assertEquals(0, fcq.size());
|
|
|
+ assertEquals(3, fcq2.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testDrainToWithLimit() {
|
|
|
+ Configuration conf = new Configuration();
|
|
|
+ conf.setInt("ns." + IPC_CALLQUEUE_PRIORITY_LEVELS_KEY, 2);
|
|
|
+ FairCallQueue<Schedulable> fcq2 = new FairCallQueue<Schedulable>(10, "ns", conf);
|
|
|
+
|
|
|
+ fcq.setScheduler(alwaysZeroScheduler);
|
|
|
+ fcq2.setScheduler(alwaysZeroScheduler);
|
|
|
+
|
|
|
+ // Start with 3 in fcq, to be drained
|
|
|
+ for (int i = 0; i < 3; i++) {
|
|
|
+ fcq.offer(mockCall("c"));
|
|
|
+ }
|
|
|
+
|
|
|
+ fcq.drainTo(fcq2, 2);
|
|
|
+
|
|
|
+ assertEquals(1, fcq.size());
|
|
|
+ assertEquals(2, fcq2.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testInitialRemainingCapacity() {
|
|
|
+ assertEquals(10, fcq.remainingCapacity());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testFirstQueueFullRemainingCapacity() {
|
|
|
+ fcq.setScheduler(alwaysZeroScheduler);
|
|
|
+ while (fcq.offer(mockCall("c"))) ; // Queue 0 will fill up first, then queue 1
|
|
|
+
|
|
|
+ assertEquals(5, fcq.remainingCapacity());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testAllQueuesFullRemainingCapacity() {
|
|
|
+ RpcScheduler sched = mock(RpcScheduler.class);
|
|
|
+ when(sched.getPriorityLevel(Matchers.<Schedulable>any())).thenReturn(0, 0, 0, 0, 0, 1, 1, 1, 1, 1);
|
|
|
+ fcq.setScheduler(sched);
|
|
|
+ while (fcq.offer(mockCall("c"))) ;
|
|
|
+
|
|
|
+ assertEquals(0, fcq.remainingCapacity());
|
|
|
+ assertEquals(10, fcq.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testQueuesPartialFilledRemainingCapacity() {
|
|
|
+ RpcScheduler sched = mock(RpcScheduler.class);
|
|
|
+ when(sched.getPriorityLevel(Matchers.<Schedulable>any())).thenReturn(0, 1, 0, 1, 0);
|
|
|
+ fcq.setScheduler(sched);
|
|
|
+ for (int i = 0; i < 5; i++) { fcq.offer(mockCall("c")); }
|
|
|
+
|
|
|
+ assertEquals(5, fcq.remainingCapacity());
|
|
|
+ assertEquals(5, fcq.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Putter produces FakeCalls
|
|
|
+ */
|
|
|
+ public class Putter implements Runnable {
|
|
|
+ private final BlockingQueue<Schedulable> cq;
|
|
|
+
|
|
|
+ public final String tag;
|
|
|
+ public volatile int callsAdded = 0; // How many calls we added, accurate unless interrupted
|
|
|
+ private final int maxCalls;
|
|
|
+
|
|
|
+ public Putter(BlockingQueue<Schedulable> aCq, int maxCalls, String tag) {
|
|
|
+ this.maxCalls = maxCalls;
|
|
|
+ this.cq = aCq;
|
|
|
+ this.tag = tag;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getTag() {
|
|
|
+ if (this.tag != null) return this.tag;
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ try {
|
|
|
+ // Fill up to max (which is infinite if maxCalls < 0)
|
|
|
+ while (callsAdded < maxCalls || maxCalls < 0) {
|
|
|
+ cq.put(mockCall(getTag()));
|
|
|
+ callsAdded++;
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Taker consumes FakeCalls
|
|
|
+ */
|
|
|
+ public class Taker implements Runnable {
|
|
|
+ private final BlockingQueue<Schedulable> cq;
|
|
|
+
|
|
|
+ public final String tag; // if >= 0 means we will only take the matching tag, and put back
|
|
|
+ // anything else
|
|
|
+ public volatile int callsTaken = 0; // total calls taken, accurate if we aren't interrupted
|
|
|
+ public volatile Schedulable lastResult = null; // the last thing we took
|
|
|
+ private final int maxCalls; // maximum calls to take
|
|
|
+
|
|
|
+ private IdentityProvider uip;
|
|
|
+
|
|
|
+ public Taker(BlockingQueue<Schedulable> aCq, int maxCalls, String tag) {
|
|
|
+ this.maxCalls = maxCalls;
|
|
|
+ this.cq = aCq;
|
|
|
+ this.tag = tag;
|
|
|
+ this.uip = new UserIdentityProvider();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ try {
|
|
|
+ // Take while we don't exceed maxCalls, or if maxCalls is undefined (< 0)
|
|
|
+ while (callsTaken < maxCalls || maxCalls < 0) {
|
|
|
+ Schedulable res = cq.take();
|
|
|
+ String identity = uip.makeIdentity(res);
|
|
|
+
|
|
|
+ if (tag != null && this.tag.equals(identity)) {
|
|
|
+ // This call does not match our tag, we should put it back and try again
|
|
|
+ cq.put(res);
|
|
|
+ } else {
|
|
|
+ callsTaken++;
|
|
|
+ lastResult = res;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Assert we can take exactly the numberOfTakes
|
|
|
+ public void assertCanTake(BlockingQueue<Schedulable> cq, int numberOfTakes,
|
|
|
+ int takeAttempts) throws InterruptedException {
|
|
|
+
|
|
|
+ Taker taker = new Taker(cq, takeAttempts, "default");
|
|
|
+ Thread t = new Thread(taker);
|
|
|
+ t.start();
|
|
|
+ t.join(100);
|
|
|
+
|
|
|
+ assertEquals(numberOfTakes, taker.callsTaken);
|
|
|
+ t.interrupt();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Assert we can put exactly the numberOfPuts
|
|
|
+ public void assertCanPut(BlockingQueue<Schedulable> cq, int numberOfPuts,
|
|
|
+ int putAttempts) throws InterruptedException {
|
|
|
+
|
|
|
+ Putter putter = new Putter(cq, putAttempts, null);
|
|
|
+ Thread t = new Thread(putter);
|
|
|
+ t.start();
|
|
|
+ t.join(100);
|
|
|
+
|
|
|
+ assertEquals(numberOfPuts, putter.callsAdded);
|
|
|
+ t.interrupt();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Make sure put will overflow into lower queues when the top is full
|
|
|
+ public void testPutOverflows() throws InterruptedException {
|
|
|
+ fcq.setScheduler(alwaysZeroScheduler);
|
|
|
+
|
|
|
+ // We can fit more than 5, even though the scheduler suggests the top queue
|
|
|
+ assertCanPut(fcq, 8, 8);
|
|
|
+ assertEquals(8, fcq.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testPutBlocksWhenAllFull() throws InterruptedException {
|
|
|
+ fcq.setScheduler(alwaysZeroScheduler);
|
|
|
+
|
|
|
+ assertCanPut(fcq, 10, 10); // Fill up
|
|
|
+ assertEquals(10, fcq.size());
|
|
|
+
|
|
|
+ // Put more which causes overflow
|
|
|
+ assertCanPut(fcq, 0, 1); // Will block
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testTakeBlocksWhenEmpty() throws InterruptedException {
|
|
|
+ fcq.setScheduler(alwaysZeroScheduler);
|
|
|
+ assertCanTake(fcq, 0, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testTakeRemovesCall() throws InterruptedException {
|
|
|
+ fcq.setScheduler(alwaysZeroScheduler);
|
|
|
+ Schedulable call = mockCall("c");
|
|
|
+ fcq.offer(call);
|
|
|
+
|
|
|
+ assertEquals(call, fcq.take());
|
|
|
+ assertEquals(0, fcq.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testTakeTriesNextQueue() throws InterruptedException {
|
|
|
+ // Make a FCQ filled with calls in q 1 but empty in q 0
|
|
|
+ RpcScheduler q1Scheduler = mock(RpcScheduler.class);
|
|
|
+ when(q1Scheduler.getPriorityLevel(Matchers.<Schedulable>any())).thenReturn(1);
|
|
|
+ fcq.setScheduler(q1Scheduler);
|
|
|
+
|
|
|
+ // A mux which only draws from q 0
|
|
|
+ RpcMultiplexer q0mux = mock(RpcMultiplexer.class);
|
|
|
+ when(q0mux.getAndAdvanceCurrentIndex()).thenReturn(0);
|
|
|
+ fcq.setMultiplexer(q0mux);
|
|
|
+
|
|
|
+ Schedulable call = mockCall("c");
|
|
|
+ fcq.put(call);
|
|
|
+
|
|
|
+ // Take from q1 even though mux said q0, since q0 empty
|
|
|
+ assertEquals(call, fcq.take());
|
|
|
+ assertEquals(0, fcq.size());
|
|
|
+ }
|
|
|
+}
|