1   /**
2    * Copyright 2011 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase.ipc;
21  
22  import static org.junit.Assert.assertEquals;
23  import static org.junit.Assert.assertFalse;
24  import static org.junit.Assert.assertTrue;
25  import static org.junit.Assert.fail;
26  
27  import java.io.IOException;
28  import java.lang.reflect.Method;
29  import java.net.InetSocketAddress;
30  import java.util.ArrayList;
31  import java.util.List;
32  
33  import org.apache.hadoop.conf.Configuration;
34  import org.apache.hadoop.hbase.HBaseConfiguration;
35  import org.apache.hadoop.hbase.ipc.VersionedProtocol;
36  import org.apache.log4j.AppenderSkeleton;
37  import org.apache.log4j.Logger;
38  import org.apache.log4j.spi.LoggingEvent;
39  import org.junit.Test;
40  
41  /**
42   * Test that delayed RPCs work. Fire up three calls, the first of which should
43   * be delayed. Check that the last two, which are undelayed, return before the
44   * first one.
45   */
46  public class TestDelayedRpc {
47    public static RpcServer rpcServer;
48  
49    public static final int UNDELAYED = 0;
50    public static final int DELAYED = 1;
51  
52    @Test
53    public void testDelayedRpcImmediateReturnValue() throws Exception {
54      testDelayedRpc(false);
55    }
56  
57    @Test
58    public void testDelayedRpcDelayedReturnValue() throws Exception {
59      testDelayedRpc(true);
60    }
61  
62    private void testDelayedRpc(boolean delayReturnValue) throws Exception {
63      Configuration conf = HBaseConfiguration.create();
64      InetSocketAddress isa = new InetSocketAddress("localhost", 0);
65  
66      rpcServer = HBaseRPC.getServer(new TestRpcImpl(delayReturnValue),
67          new Class<?>[]{ TestRpcImpl.class },
68          isa.getHostName(), isa.getPort(), 1, 0, true, conf, 0);
69      rpcServer.start();
70  
71      TestRpc client = (TestRpc) HBaseRPC.getProxy(TestRpc.class, 0,
72          rpcServer.getListenerAddress(), conf, 1000);
73  
74      List<Integer> results = new ArrayList<Integer>();
75  
76      TestThread th1 = new TestThread(client, true, results);
77      TestThread th2 = new TestThread(client, false, results);
78      TestThread th3 = new TestThread(client, false, results);
79      th1.start();
80      Thread.sleep(100);
81      th2.start();
82      Thread.sleep(200);
83      th3.start();
84  
85      th1.join();
86      th2.join();
87      th3.join();
88  
89      assertEquals(UNDELAYED, results.get(0).intValue());
90      assertEquals(UNDELAYED, results.get(1).intValue());
91      assertEquals(results.get(2).intValue(), delayReturnValue ? DELAYED :
92          0xDEADBEEF);
93    }
94  
95    private static class ListAppender extends AppenderSkeleton {
96      private List<String> messages = new ArrayList<String>();
97  
98      @Override
99      protected void append(LoggingEvent event) {
100       messages.add(event.getMessage().toString());
101     }
102 
103     @Override
104     public void close() {
105     }
106 
107     @Override
108     public boolean requiresLayout() {
109       return false;
110     }
111 
112     public List<String> getMessages() {
113       return messages;
114     }
115   }
116 
117   @Test
118   public void testTooManyDelayedRpcs() throws Exception {
119     Configuration conf = HBaseConfiguration.create();
120     final int MAX_DELAYED_RPC = 10;
121     conf.setInt("hbase.ipc.warn.delayedrpc.number", MAX_DELAYED_RPC);
122 
123     ListAppender listAppender = new ListAppender();
124     Logger log = Logger.getLogger("org.apache.hadoop.ipc.HBaseServer");
125     log.addAppender(listAppender);
126 
127     InetSocketAddress isa = new InetSocketAddress("localhost", 0);
128     rpcServer = HBaseRPC.getServer(new TestRpcImpl(true),
129         new Class<?>[]{ TestRpcImpl.class },
130         isa.getHostName(), isa.getPort(), 1, 0, true, conf, 0);
131     rpcServer.start();
132     TestRpc client = (TestRpc) HBaseRPC.getProxy(TestRpc.class, 0,
133         rpcServer.getListenerAddress(), conf, 1000);
134 
135     Thread threads[] = new Thread[MAX_DELAYED_RPC + 1];
136 
137     for (int i = 0; i < MAX_DELAYED_RPC; i++) {
138       threads[i] = new TestThread(client, true, null);
139       threads[i].start();
140     }
141 
142     /* No warnings till here. */
143     assertTrue(listAppender.getMessages().isEmpty());
144 
145     /* This should give a warning. */
146     threads[MAX_DELAYED_RPC] = new TestThread(client, true, null);
147     threads[MAX_DELAYED_RPC].start();
148 
149     for (int i = 0; i < MAX_DELAYED_RPC; i++) {
150       threads[i].join();
151     }
152 
153     assertFalse(listAppender.getMessages().isEmpty());
154     assertTrue(listAppender.getMessages().get(0).startsWith(
155         "Too many delayed calls"));
156 
157     log.removeAppender(listAppender);
158   }
159 
160   public interface TestRpc extends VersionedProtocol {
161     public static final long VERSION = 1L;
162     int test(boolean delay);
163   }
164 
165   private static class TestRpcImpl implements TestRpc {
166     /**
167      * Should the return value of delayed call be set at the end of the delay
168      * or at call return.
169      */
170     private boolean delayReturnValue;
171 
172     /**
173      * @param delayReturnValue Should the response to the delayed call be set
174      * at the start or the end of the delay.
175      * @param delay Amount of milliseconds to delay the call by
176      */
177     public TestRpcImpl(boolean delayReturnValue) {
178       this.delayReturnValue = delayReturnValue;
179     }
180 
181     @Override
182     public int test(final boolean delay) {
183       if (!delay) {
184         return UNDELAYED;
185       }
186       final Delayable call = rpcServer.getCurrentCall();
187       call.startDelay(delayReturnValue);
188       new Thread() {
189         public void run() {
190           try {
191             Thread.sleep(500);
192             call.endDelay(delayReturnValue ? DELAYED : null);
193           } catch (Exception e) {
194             e.printStackTrace();
195           }
196         }
197       }.start();
198       // This value should go back to client only if the response is set
199       // immediately at delay time.
200       return 0xDEADBEEF;
201     }
202 
203     @Override
204     public long getProtocolVersion(String arg0, long arg1) throws IOException {
205       return 0;
206     }
207 
208     @Override
209     public ProtocolSignature getProtocolSignature(String protocol,
210         long clientVersion, int clientMethodsHash) throws IOException {
211       Method [] methods = this.getClass().getMethods();
212       int [] hashes = new int [methods.length];
213       for (int i = 0; i < methods.length; i++) {
214         hashes[i] = methods[i].hashCode();
215       }
216       return new ProtocolSignature(clientVersion, hashes);
217     }
218   }
219 
220   private static class TestThread extends Thread {
221     private TestRpc server;
222     private boolean delay;
223     private List<Integer> results;
224 
225     public TestThread(TestRpc server, boolean delay, List<Integer> results) {
226       this.server = server;
227       this.delay = delay;
228       this.results = results;
229     }
230 
231     @Override
232     public void run() {
233       Integer result = new Integer(server.test(delay));
234       if (results != null) {
235         synchronized (results) {
236           results.add(result);
237         }
238       }
239     }
240   }
241 
242   @Test
243   public void testEndDelayThrowing() throws IOException {
244     Configuration conf = HBaseConfiguration.create();
245     InetSocketAddress isa = new InetSocketAddress("localhost", 0);
246 
247     rpcServer = HBaseRPC.getServer(new FaultyTestRpc(),
248         new Class<?>[]{ TestRpcImpl.class },
249         isa.getHostName(), isa.getPort(), 1, 0, true, conf, 0);
250     rpcServer.start();
251 
252     TestRpc client = (TestRpc) HBaseRPC.getProxy(TestRpc.class, 0,
253         rpcServer.getListenerAddress(), conf, 1000);
254 
255     int result = 0xDEADBEEF;
256 
257     try {
258       result = client.test(false);
259     } catch (Exception e) {
260       fail("No exception should have been thrown.");
261     }
262     assertEquals(result, UNDELAYED);
263 
264     boolean caughtException = false;
265     try {
266       result = client.test(true);
267     } catch(Exception e) {
268       // Exception thrown by server is enclosed in a RemoteException.
269       if (e.getCause().getMessage().startsWith(
270           "java.lang.Exception: Something went wrong"))
271         caughtException = true;
272     }
273     assertTrue(caughtException);
274   }
275 
276   /**
277    * Delayed calls to this class throw an exception.
278    */
279   private static class FaultyTestRpc implements TestRpc {
280     @Override
281     public int test(boolean delay) {
282       if (!delay)
283         return UNDELAYED;
284       Delayable call = rpcServer.getCurrentCall();
285       call.startDelay(true);
286       try {
287         call.endDelayThrowing(new Exception("Something went wrong"));
288       } catch (IOException e) {
289         e.printStackTrace();
290       }
291       // Client will receive the Exception, not this value.
292       return DELAYED;
293     }
294 
295     @Override
296     public long getProtocolVersion(String arg0, long arg1) throws IOException {
297       return 0;
298     }
299 
300     @Override
301     public ProtocolSignature getProtocolSignature(String protocol,
302         long clientVersion, int clientMethodsHash) throws IOException {
303       return new ProtocolSignature(clientVersion, new int [] {});
304     }
305   }
306 }