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 junit.framework.Assert;
34  import org.apache.hadoop.conf.Configuration;
35  import org.apache.hadoop.hbase.*;
36  import org.apache.hadoop.hbase.ipc.VersionedProtocol;
37  import org.apache.log4j.AppenderSkeleton;
38  import org.apache.log4j.Logger;
39  import org.apache.log4j.spi.LoggingEvent;
40  import org.apache.log4j.Level;
41  import org.junit.Test;
42  import org.junit.experimental.categories.Category;
43  
44  /**
45   * Test that delayed RPCs work. Fire up three calls, the first of which should
46   * be delayed. Check that the last two, which are undelayed, return before the
47   * first one.
48   */
49  @Category(MediumTests.class) // Fails sometimes with small tests
50  public class TestDelayedRpc {
51    public static RpcServer rpcServer;
52  
53    public static final int UNDELAYED = 0;
54    public static final int DELAYED = 1;
55  
56    @Test
57    public void testDelayedRpcImmediateReturnValue() throws Exception {
58      testDelayedRpc(false);
59    }
60  
61    @Test
62    public void testDelayedRpcDelayedReturnValue() throws Exception {
63      testDelayedRpc(true);
64    }
65  
66    private void testDelayedRpc(boolean delayReturnValue) throws Exception {
67      Configuration conf = HBaseConfiguration.create();
68      InetSocketAddress isa = new InetSocketAddress("localhost", 0);
69  
70      rpcServer = HBaseRPC.getServer(new TestRpcImpl(delayReturnValue),
71          new Class<?>[]{ TestRpcImpl.class },
72          isa.getHostName(), isa.getPort(), 1, 0, true, conf, 0);
73      RpcEngine rpcEngine = null;
74      try {
75        rpcServer.start();
76        rpcEngine = HBaseRPC.getProtocolEngine(conf);
77  
78        TestRpc client = rpcEngine.getProxy(TestRpc.class, 0,
79          rpcServer.getListenerAddress(), conf, 1000);
80  
81        List<Integer> results = new ArrayList<Integer>();
82  
83        TestThread th1 = new TestThread(client, true, results);
84        TestThread th2 = new TestThread(client, false, results);
85        TestThread th3 = new TestThread(client, false, results);
86        th1.start();
87        Thread.sleep(100);
88        th2.start();
89        Thread.sleep(200);
90        th3.start();
91  
92        th1.join();
93        th2.join();
94        th3.join();
95  
96        assertEquals(UNDELAYED, results.get(0).intValue());
97        assertEquals(UNDELAYED, results.get(1).intValue());
98        assertEquals(results.get(2).intValue(), delayReturnValue ? DELAYED :
99            0xDEADBEEF);
100     } finally {
101       if (rpcEngine != null) {
102         rpcEngine.close();
103       }
104     }
105   }
106 
107   private static class ListAppender extends AppenderSkeleton {
108     private List<String> messages = new ArrayList<String>();
109 
110     @Override
111     protected void append(LoggingEvent event) {
112       messages.add(event.getMessage().toString());
113     }
114 
115     @Override
116     public void close() {
117     }
118 
119     @Override
120     public boolean requiresLayout() {
121       return false;
122     }
123 
124     public List<String> getMessages() {
125       return messages;
126     }
127   }
128 
129   @Test
130   public void testTooManyDelayedRpcs() throws Exception {
131     Configuration conf = HBaseConfiguration.create();
132     final int MAX_DELAYED_RPC = 10;
133     conf.setInt("hbase.ipc.warn.delayedrpc.number", MAX_DELAYED_RPC);
134 
135     ListAppender listAppender = new ListAppender();
136     Logger log = Logger.getLogger("org.apache.hadoop.ipc.HBaseServer");
137     log.addAppender(listAppender);
138     log.setLevel(Level.WARN);
139 
140     InetSocketAddress isa = new InetSocketAddress("localhost", 0);
141     rpcServer = HBaseRPC.getServer(new TestRpcImpl(true),
142         new Class<?>[]{ TestRpcImpl.class },
143         isa.getHostName(), isa.getPort(), 1, 0, true, conf, 0);
144     RpcEngine rpcEngine = null;
145     try {
146       rpcServer.start();
147       rpcEngine = HBaseRPC.getProtocolEngine(conf);
148 
149       TestRpc client = rpcEngine.getProxy(TestRpc.class, 0,
150           rpcServer.getListenerAddress(), conf, 1000);
151 
152       Thread threads[] = new Thread[MAX_DELAYED_RPC + 1];
153 
154       for (int i = 0; i < MAX_DELAYED_RPC; i++) {
155         threads[i] = new TestThread(client, true, null);
156         threads[i].start();
157       }
158 
159       /* No warnings till here. */
160       assertTrue(listAppender.getMessages().isEmpty());
161 
162       /* This should give a warning. */
163       threads[MAX_DELAYED_RPC] = new TestThread(client, true, null);
164       threads[MAX_DELAYED_RPC].start();
165 
166       for (int i = 0; i < MAX_DELAYED_RPC; i++) {
167         threads[i].join();
168       }
169 
170       assertFalse(listAppender.getMessages().isEmpty());
171       assertTrue(listAppender.getMessages().get(0).startsWith(
172           "Too many delayed calls"));
173 
174       log.removeAppender(listAppender);
175     } finally {
176       if (rpcEngine != null) {
177         rpcEngine.close();
178       }
179     }
180   }
181 
182   public interface TestRpc extends VersionedProtocol {
183     public static final long VERSION = 1L;
184     int test(boolean delay);
185   }
186 
187   private static class TestRpcImpl implements TestRpc {
188     /**
189      * Should the return value of delayed call be set at the end of the delay
190      * or at call return.
191      */
192     private boolean delayReturnValue;
193 
194     /**
195      * @param delayReturnValue Should the response to the delayed call be set
196      * at the start or the end of the delay.
197      */
198     public TestRpcImpl(boolean delayReturnValue) {
199       this.delayReturnValue = delayReturnValue;
200     }
201 
202     @Override
203     public int test(final boolean delay) {
204       if (!delay) {
205         return UNDELAYED;
206       }
207       final Delayable call = HBaseServer.getCurrentCall();
208       call.startDelay(delayReturnValue);
209       new Thread() {
210         public void run() {
211           try {
212             Thread.sleep(500);
213             call.endDelay(delayReturnValue ? DELAYED : null);
214           } catch (Exception e) {
215             e.printStackTrace();
216           }
217         }
218       }.start();
219       // This value should go back to client only if the response is set
220       // immediately at delay time.
221       return 0xDEADBEEF;
222     }
223 
224     @Override
225     public long getProtocolVersion(String arg0, long arg1) throws IOException {
226       return 0;
227     }
228 
229     @Override
230     public ProtocolSignature getProtocolSignature(String protocol,
231         long clientVersion, int clientMethodsHash) throws IOException {
232       Method [] methods = this.getClass().getMethods();
233       int [] hashes = new int [methods.length];
234       for (int i = 0; i < methods.length; i++) {
235         hashes[i] = methods[i].hashCode();
236       }
237       return new ProtocolSignature(clientVersion, hashes);
238     }
239   }
240 
241   private static class TestThread extends Thread {
242     private TestRpc server;
243     private boolean delay;
244     private List<Integer> results;
245 
246     public TestThread(TestRpc server, boolean delay, List<Integer> results) {
247       this.server = server;
248       this.delay = delay;
249       this.results = results;
250     }
251 
252     @Override
253     public void run() {
254       try {
255         Integer result = new Integer(server.test(delay));
256         if (results != null) {
257           synchronized (results) {
258             results.add(result);
259           }
260         }
261       } catch (Exception e) {
262          fail("Unexpected exception: "+e.getMessage());
263       }
264     }
265   }
266 
267   @Test
268   public void testEndDelayThrowing() throws IOException {
269     Configuration conf = HBaseConfiguration.create();
270     InetSocketAddress isa = new InetSocketAddress("localhost", 0);
271 
272     rpcServer = HBaseRPC.getServer(new FaultyTestRpc(),
273         new Class<?>[]{ TestRpcImpl.class },
274         isa.getHostName(), isa.getPort(), 1, 0, true, conf, 0);
275     RpcEngine rpcEngine = null;
276     try {
277       rpcServer.start();
278       rpcEngine = HBaseRPC.getProtocolEngine(conf);
279 
280       TestRpc client = rpcEngine.getProxy(TestRpc.class, 0,
281           rpcServer.getListenerAddress(), conf, 1000);
282 
283       int result = 0xDEADBEEF;
284 
285       try {
286         result = client.test(false);
287       } catch (Exception e) {
288         fail("No exception should have been thrown.");
289       }
290       assertEquals(result, UNDELAYED);
291 
292       boolean caughtException = false;
293       try {
294         result = client.test(true);
295       } catch(Exception e) {
296         // Exception thrown by server is enclosed in a RemoteException.
297         if (e.getCause().getMessage().startsWith(
298             "java.lang.Exception: Something went wrong"))
299           caughtException = true;
300       }
301       assertTrue(caughtException);
302     } finally {
303       if (rpcEngine != null) {
304         rpcEngine.close();
305       }
306     }
307   }
308 
309   /**
310    * Delayed calls to this class throw an exception.
311    */
312   private static class FaultyTestRpc implements TestRpc {
313     @Override
314     public int test(boolean delay) {
315       if (!delay)
316         return UNDELAYED;
317       Delayable call = HBaseServer.getCurrentCall();
318       call.startDelay(true);
319       try {
320         call.endDelayThrowing(new Exception("Something went wrong"));
321       } catch (IOException e) {
322         e.printStackTrace();
323       }
324       // Client will receive the Exception, not this value.
325       return DELAYED;
326     }
327 
328     @Override
329     public long getProtocolVersion(String arg0, long arg1) throws IOException {
330       return 0;
331     }
332 
333     @Override
334     public ProtocolSignature getProtocolSignature(String protocol,
335         long clientVersion, int clientMethodsHash) throws IOException {
336       return new ProtocolSignature(clientVersion, new int [] {});
337     }
338   }
339 
340   @org.junit.Rule
341   public org.apache.hadoop.hbase.ResourceCheckerJUnitRule cu =
342     new org.apache.hadoop.hbase.ResourceCheckerJUnitRule();
343 }
344