|
17 | 17 | */ |
18 | 18 | package org.apache.hadoop.hbase.ipc; |
19 | 19 |
|
| 20 | +import static org.awaitility.Awaitility.await; |
20 | 21 | import static org.junit.Assert.assertEquals; |
21 | 22 | import static org.junit.Assert.assertFalse; |
22 | 23 | import static org.junit.Assert.assertNotEquals; |
|
34 | 35 | import java.lang.reflect.Field; |
35 | 36 | import java.net.InetSocketAddress; |
36 | 37 | import java.util.ArrayList; |
| 38 | +import java.util.Collections; |
37 | 39 | import java.util.HashSet; |
38 | 40 | import java.util.List; |
39 | 41 | import java.util.Map; |
40 | 42 | import java.util.Set; |
41 | 43 | import java.util.concurrent.BlockingQueue; |
42 | 44 | import java.util.concurrent.CountDownLatch; |
43 | 45 | import java.util.concurrent.LinkedBlockingQueue; |
| 46 | +import java.util.concurrent.TimeUnit; |
44 | 47 | import org.apache.hadoop.conf.Configuration; |
45 | 48 | import org.apache.hadoop.hbase.Abortable; |
46 | 49 | import org.apache.hadoop.hbase.HBaseClassTestRule; |
@@ -814,4 +817,166 @@ public void drop() { |
814 | 817 | } |
815 | 818 | }; |
816 | 819 | } |
| 820 | + |
| 821 | + /** |
| 822 | + * Test LIFO switching behavior through actual RPC calls. This test verifies that when the queue |
| 823 | + * fills beyond the LIFO threshold, newer calls are processed before older calls (LIFO mode). |
| 824 | + */ |
| 825 | + @Test |
| 826 | + public void testCoDelLifoWithRpcCalls() throws Exception { |
| 827 | + Configuration testConf = HBaseConfiguration.create(); |
| 828 | + testConf.set(RpcExecutor.CALL_QUEUE_TYPE_CONF_KEY, |
| 829 | + RpcExecutor.CALL_QUEUE_TYPE_CODEL_CONF_VALUE); |
| 830 | + int maxCallQueueLength = 50; |
| 831 | + double codelLifoThreshold = 0.8; |
| 832 | + testConf.setInt(RpcScheduler.IPC_SERVER_MAX_CALLQUEUE_LENGTH, maxCallQueueLength); |
| 833 | + testConf.setDouble(RpcExecutor.CALL_QUEUE_CODEL_LIFO_THRESHOLD, codelLifoThreshold); |
| 834 | + testConf.setInt(RpcExecutor.CALL_QUEUE_CODEL_TARGET_DELAY, 100); |
| 835 | + testConf.setInt(RpcExecutor.CALL_QUEUE_CODEL_INTERVAL, 100); |
| 836 | + testConf.setInt(HConstants.REGION_SERVER_HANDLER_COUNT, 1); // Single handler to control |
| 837 | + // processing |
| 838 | + |
| 839 | + PriorityFunction priority = mock(PriorityFunction.class); |
| 840 | + when(priority.getPriority(any(), any(), any())).thenReturn(HConstants.NORMAL_QOS); |
| 841 | + SimpleRpcScheduler scheduler = |
| 842 | + new SimpleRpcScheduler(testConf, 1, 0, 0, priority, HConstants.QOS_THRESHOLD); |
| 843 | + |
| 844 | + try { |
| 845 | + scheduler.init(CONTEXT); |
| 846 | + scheduler.start(); |
| 847 | + |
| 848 | + // Track completion order |
| 849 | + final List<Integer> completedCalls = Collections.synchronizedList(new ArrayList<>()); |
| 850 | + |
| 851 | + // Dispatch many slow calls rapidly to fill the queue beyond 80% threshold |
| 852 | + // With queue limit of 50, we need > 40 calls to cross 80% |
| 853 | + int numCalls = 48; |
| 854 | + for (int i = 0; i < numCalls; i++) { |
| 855 | + final int callId = i; |
| 856 | + CallRunner call = createMockTask(HConstants.NORMAL_QOS); |
| 857 | + call.setStatus(new MonitoredRPCHandlerImpl("test")); |
| 858 | + doAnswer(invocation -> { |
| 859 | + completedCalls.add(callId); |
| 860 | + Thread.sleep(100); // Slow processing to allow queue to build up |
| 861 | + return null; |
| 862 | + }).when(call).run(); |
| 863 | + scheduler.dispatch(call); |
| 864 | + // No delay between dispatches - rapidly fill the queue |
| 865 | + } |
| 866 | + |
| 867 | + // Wait for some calls to complete |
| 868 | + await().atMost(1, TimeUnit.SECONDS).until(() -> completedCalls.size() >= 2); |
| 869 | + |
| 870 | + // Check that we had LIFO switches |
| 871 | + long lifoSwitches = scheduler.getNumLifoModeSwitches(); |
| 872 | + assertTrue("Should have switched to LIFO mode at least once, but got: " + lifoSwitches, |
| 873 | + lifoSwitches > 0); |
| 874 | + |
| 875 | + // Verify LIFO behavior: Among first completed calls, we should see higher call IDs |
| 876 | + // (indicating later dispatched calls completed first) |
| 877 | + int maxCallIdCompleted = -1; |
| 878 | + for (int i = 0; i < 5 && i < completedCalls.size(); i++) { |
| 879 | + maxCallIdCompleted = Math.max(maxCallIdCompleted, completedCalls.get(i)); |
| 880 | + } |
| 881 | + // At least one of the early completed calls should have a high ID (>20) |
| 882 | + // indicating LIFO processing |
| 883 | + assertTrue( |
| 884 | + "Expected LIFO behavior: early completed calls should include call arrived after threshold " |
| 885 | + + "maxCallIdCompleted: " + maxCallIdCompleted, |
| 886 | + maxCallIdCompleted > maxCallQueueLength * codelLifoThreshold); |
| 887 | + |
| 888 | + } finally { |
| 889 | + scheduler.stop(); |
| 890 | + } |
| 891 | + } |
| 892 | + |
| 893 | + /** |
| 894 | + * Test that CoDel queue returns to FIFO mode after draining below threshold. |
| 895 | + */ |
| 896 | + @Test |
| 897 | + public void testCoDelQueueDrainAndFifoReturn() throws Exception { |
| 898 | + Configuration testConf = HBaseConfiguration.create(); |
| 899 | + testConf.set(RpcExecutor.CALL_QUEUE_TYPE_CONF_KEY, |
| 900 | + RpcExecutor.CALL_QUEUE_TYPE_CODEL_CONF_VALUE); |
| 901 | + testConf.setInt(RpcScheduler.IPC_SERVER_MAX_CALLQUEUE_LENGTH, 50); |
| 902 | + testConf.setDouble(RpcExecutor.CALL_QUEUE_CODEL_LIFO_THRESHOLD, 0.8); |
| 903 | + testConf.setInt(HConstants.REGION_SERVER_HANDLER_COUNT, 2); |
| 904 | + |
| 905 | + PriorityFunction priority = mock(PriorityFunction.class); |
| 906 | + when(priority.getPriority(any(), any(), any())).thenReturn(HConstants.NORMAL_QOS); |
| 907 | + SimpleRpcScheduler scheduler = |
| 908 | + new SimpleRpcScheduler(testConf, 2, 0, 0, priority, HConstants.QOS_THRESHOLD); |
| 909 | + |
| 910 | + try { |
| 911 | + scheduler.init(CONTEXT); |
| 912 | + scheduler.start(); |
| 913 | + |
| 914 | + final List<Integer> completedCalls = Collections.synchronizedList(new ArrayList<>()); |
| 915 | + |
| 916 | + // Phase 1: Fill queue rapidly to trigger LIFO (>40 calls for 80% of 50) |
| 917 | + for (int i = 0; i < 48; i++) { |
| 918 | + final int callId = i; |
| 919 | + CallRunner call = createMockTask(HConstants.NORMAL_QOS); |
| 920 | + call.setStatus(new MonitoredRPCHandlerImpl("test")); |
| 921 | + doAnswer(invocation -> { |
| 922 | + completedCalls.add(callId); |
| 923 | + Thread.sleep(80); |
| 924 | + return null; |
| 925 | + }).when(call).run(); |
| 926 | + scheduler.dispatch(call); |
| 927 | + } |
| 928 | + |
| 929 | + // Wait for calls to complete |
| 930 | + await().atMost(1, TimeUnit.SECONDS).until(() -> completedCalls.size() >= 2); |
| 931 | + long lifoSwitchesPhase1 = scheduler.getNumLifoModeSwitches(); |
| 932 | + assertTrue("Should have entered LIFO mode", lifoSwitchesPhase1 > 0); |
| 933 | + |
| 934 | + // Phase 2: Let queue drain |
| 935 | + await().atMost(2, TimeUnit.SECONDS).until(() -> scheduler.getGeneralQueueLength() <= 0); |
| 936 | + int queueLength = scheduler.getGeneralQueueLength(); |
| 937 | + assertTrue("Queue should have drained significantly, but got: " + queueLength, |
| 938 | + queueLength < 25); |
| 939 | + |
| 940 | + // Phase 3: Send new calls - should process in FIFO order |
| 941 | + completedCalls.clear(); |
| 942 | + for (int i = 100; i < 105; i++) { |
| 943 | + final int callId = i; |
| 944 | + CallRunner call = createMockTask(HConstants.NORMAL_QOS); |
| 945 | + call.setStatus(new MonitoredRPCHandlerImpl("test")); |
| 946 | + doAnswer(invocation -> { |
| 947 | + completedCalls.add(callId); |
| 948 | + Thread.sleep(80); |
| 949 | + return null; |
| 950 | + }).when(call).run(); |
| 951 | + scheduler.dispatch(call); |
| 952 | + } |
| 953 | + |
| 954 | + // Wait for these calls to complete |
| 955 | + await().atMost(2, TimeUnit.SECONDS).until(() -> completedCalls.size() >= 2); |
| 956 | + |
| 957 | + // Verify FIFO behavior: calls should complete in order (100, 101, 102, 103, 104) |
| 958 | + assertTrue( |
| 959 | + "Should have completed test calls, but count of completed calls: " + completedCalls.size(), |
| 960 | + completedCalls.size() >= 2); |
| 961 | + // Check that calls completed roughly in order (allowing some variance due to threading with 2 |
| 962 | + // handlers) |
| 963 | + // With 2 handlers, we expect general FIFO order but some interleaving is possible |
| 964 | + // Just verify the general trend is increasing |
| 965 | + int violations = 0; |
| 966 | + for (int i = 0; i < completedCalls.size() - 1; i++) { |
| 967 | + int current = completedCalls.get(i); |
| 968 | + int next = completedCalls.get(i + 1); |
| 969 | + if (next < current) { |
| 970 | + violations++; |
| 971 | + } |
| 972 | + } |
| 973 | + // Allow at most 1 violation due to concurrent execution by 2 handlers |
| 974 | + assertTrue("Calls should complete in approximate FIFO order, violations: " + violations |
| 975 | + + ", order: " + completedCalls, violations <= 1); |
| 976 | + |
| 977 | + } finally { |
| 978 | + scheduler.stop(); |
| 979 | + } |
| 980 | + } |
| 981 | + |
817 | 982 | } |
0 commit comments