View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.ipc;
19  
20  import static org.apache.hadoop.hbase.ipc.IPCUtil.buildRequestHeader;
21  import static org.apache.hadoop.hbase.ipc.IPCUtil.createRemoteException;
22  import static org.apache.hadoop.hbase.ipc.IPCUtil.getTotalSizeWhenWrittenDelimited;
23  import static org.apache.hadoop.hbase.ipc.IPCUtil.isFatalConnectionException;
24  import static org.apache.hadoop.hbase.ipc.IPCUtil.setCancelled;
25  import static org.apache.hadoop.hbase.ipc.IPCUtil.write;
26  
27  import com.google.protobuf.Message;
28  import com.google.protobuf.Message.Builder;
29  import com.google.protobuf.RpcCallback;
30  
31  import java.io.BufferedInputStream;
32  import java.io.BufferedOutputStream;
33  import java.io.DataInputStream;
34  import java.io.DataOutputStream;
35  import java.io.IOException;
36  import java.io.InputStream;
37  import java.io.InterruptedIOException;
38  import java.io.OutputStream;
39  import java.net.Socket;
40  import java.net.SocketTimeoutException;
41  import java.net.UnknownHostException;
42  import java.nio.ByteBuffer;
43  import java.security.PrivilegedExceptionAction;
44  import java.util.ArrayDeque;
45  import java.util.Locale;
46  import java.util.Queue;
47  import java.util.concurrent.ConcurrentHashMap;
48  import java.util.concurrent.ConcurrentMap;
49  import java.util.concurrent.ThreadLocalRandom;
50  
51  import javax.security.sasl.SaslException;
52  
53  import org.apache.commons.logging.Log;
54  import org.apache.commons.logging.LogFactory;
55  import org.apache.hadoop.conf.Configuration;
56  import org.apache.hadoop.hbase.CellScanner;
57  import org.apache.hadoop.hbase.DoNotRetryIOException;
58  import org.apache.hadoop.hbase.classification.InterfaceAudience;
59  import org.apache.hadoop.hbase.exceptions.ConnectionClosingException;
60  import org.apache.hadoop.hbase.io.ByteArrayOutputStream;
61  import org.apache.hadoop.hbase.ipc.HBaseRpcController.CancellationCallback;
62  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
63  import org.apache.hadoop.hbase.protobuf.generated.RPCProtos.CellBlockMeta;
64  import org.apache.hadoop.hbase.protobuf.generated.RPCProtos.ConnectionHeader;
65  import org.apache.hadoop.hbase.protobuf.generated.RPCProtos.ExceptionResponse;
66  import org.apache.hadoop.hbase.protobuf.generated.RPCProtos.RequestHeader;
67  import org.apache.hadoop.hbase.protobuf.generated.RPCProtos.ResponseHeader;
68  import org.apache.hadoop.hbase.security.HBaseSaslRpcClient;
69  import org.apache.hadoop.hbase.security.SaslUtil.QualityOfProtection;
70  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
71  import org.apache.hadoop.hbase.util.ExceptionUtil;
72  import org.apache.hadoop.io.IOUtils;
73  import org.apache.hadoop.ipc.RemoteException;
74  import org.apache.hadoop.net.NetUtils;
75  import org.apache.hadoop.security.UserGroupInformation;
76  import org.apache.htrace.Trace;
77  import org.apache.htrace.TraceScope;
78  
79  /**
80   * Thread that reads responses and notifies callers. Each connection owns a socket connected to a
81   * remote address. Calls are multiplexed through this socket: responses may be delivered out of
82   * order.
83   */
84  @InterfaceAudience.Private
85  class BlockingRpcConnection extends RpcConnection implements Runnable {
86  
87    private static final Log LOG = LogFactory.getLog(BlockingRpcConnection.class);
88  
89    private final BlockingRpcClient rpcClient;
90  
91    private final String threadName;
92    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "IS2_INCONSISTENT_SYNC",
93        justification = "We are always under lock actually")
94    private Thread thread;
95  
96    // connected socket. protected for writing UT.
97    protected Socket socket = null;
98    private DataInputStream in;
99    private DataOutputStream out;
100 
101   private HBaseSaslRpcClient saslRpcClient;
102 
103   // currently active calls
104   private final ConcurrentMap<Integer, Call> calls = new ConcurrentHashMap<>();
105 
106   private final CallSender callSender;
107 
108   private boolean closed = false;
109 
110   private byte[] connectionHeaderPreamble;
111 
112   private byte[] connectionHeaderWithLength;
113 
114   /**
115    * If the client wants to interrupt its calls easily (i.e. call Thread#interrupt), it gets into a
116    * java issue: an interruption during a write closes the socket/channel. A way to avoid this is to
117    * use a different thread for writing. This way, on interruptions, we either cancel the writes or
118    * ignore the answer if the write is already done, but we don't stop the write in the middle. This
119    * adds a thread per region server in the client, so it's kept as an option.
120    * <p>
121    * The implementation is simple: the client threads adds their call to the queue, and then wait
122    * for an answer. The CallSender blocks on the queue, and writes the calls one after the other. On
123    * interruption, the client cancels its call. The CallSender checks that the call has not been
124    * canceled before writing it.
125    * </p>
126    * When the connection closes, all the calls not yet sent are dismissed. The client thread is
127    * notified with an appropriate exception, as if the call was already sent but the answer not yet
128    * received.
129    * </p>
130    */
131   private class CallSender extends Thread {
132 
133     private final Queue<Call> callsToWrite;
134 
135     private final int maxQueueSize;
136 
137     public CallSender(String name, Configuration conf) {
138       int queueSize = conf.getInt("hbase.ipc.client.write.queueSize", 1000);
139       callsToWrite = new ArrayDeque<>(queueSize);
140       this.maxQueueSize = queueSize;
141       setDaemon(true);
142       setName(name + " - writer");
143     }
144 
145     public void sendCall(final Call call) throws IOException {
146       if (callsToWrite.size() >= maxQueueSize) {
147         throw new IOException("Can't add the call " + call.id +
148             " to the write queue. callsToWrite.size()=" + callsToWrite.size());
149       }
150       callsToWrite.offer(call);
151       BlockingRpcConnection.this.notifyAll();
152     }
153 
154     public void remove(Call call) {
155       callsToWrite.remove(call);
156       // By removing the call from the expected call list, we make the list smaller, but
157       // it means as well that we don't know how many calls we cancelled.
158       calls.remove(call.id);
159       call.setException(new CallCancelledException("Call id=" + call.id + ", waitTime=" +
160           (EnvironmentEdgeManager.currentTime() - call.getStartTime()) + ", rpcTimeout=" +
161           call.timeout));
162     }
163 
164     /**
165      * Reads the call from the queue, write them on the socket.
166      */
167     @Override
168     public void run() {
169       synchronized (BlockingRpcConnection.this) {
170         while (!closed) {
171           if (callsToWrite.isEmpty()) {
172             // We should use another monitor object here for better performance since the read
173             // thread also uses ConnectionImpl.this. But this makes the locking schema more
174             // complicated, can do it later as an optimization.
175             try {
176               BlockingRpcConnection.this.wait();
177             } catch (InterruptedException e) {
178             }
179             // check if we need to quit, so continue the main loop instead of fallback.
180             continue;
181           }
182           Call call = callsToWrite.poll();
183           if (call.isDone()) {
184             continue;
185           }
186           try {
187             tracedWriteRequest(call);
188           } catch (IOException e) {
189             // exception here means the call has not been added to the pendingCalls yet, so we need
190             // to fail it by our own.
191             if (LOG.isDebugEnabled()) {
192               LOG.debug("call write error for call #" + call.id, e);
193             }
194             call.setException(e);
195             closeConn(e);
196           }
197         }
198       }
199     }
200 
201     /**
202      * Cleans the call not yet sent when we finish.
203      */
204     public void cleanup(IOException e) {
205       IOException ie =
206           new ConnectionClosingException("Connection to " + remoteId.address + " is closing.");
207       for (Call call : callsToWrite) {
208         call.setException(ie);
209       }
210       callsToWrite.clear();
211     }
212   }
213 
214   BlockingRpcConnection(BlockingRpcClient rpcClient, ConnectionId remoteId) throws IOException {
215     super(rpcClient.conf, AbstractRpcClient.WHEEL_TIMER, remoteId, rpcClient.clusterId,
216         rpcClient.userProvider.isHBaseSecurityEnabled(), rpcClient.codec, rpcClient.compressor);
217     this.rpcClient = rpcClient;
218     if (remoteId.getAddress().isUnresolved()) {
219       throw new UnknownHostException("unknown host: " + remoteId.getAddress().getHostName());
220     }
221 
222     this.connectionHeaderPreamble = getConnectionHeaderPreamble();
223     ConnectionHeader header = getConnectionHeader();
224     ByteArrayOutputStream baos = new ByteArrayOutputStream(4 + header.getSerializedSize());
225     DataOutputStream dos = new DataOutputStream(baos);
226     dos.writeInt(header.getSerializedSize());
227     header.writeTo(dos);
228     assert baos.size() == 4 + header.getSerializedSize();
229     this.connectionHeaderWithLength = baos.getBuffer();
230 
231     UserGroupInformation ticket = remoteId.ticket.getUGI();
232     this.threadName = "IPC Client (" + this.rpcClient.socketFactory.hashCode() +
233         ") connection to " + remoteId.getAddress().toString() +
234         ((ticket == null) ? " from an unknown user" : (" from " + ticket.getUserName()));
235 
236     if (this.rpcClient.conf.getBoolean(BlockingRpcClient.SPECIFIC_WRITE_THREAD, false)) {
237       callSender = new CallSender(threadName, this.rpcClient.conf);
238       callSender.start();
239     } else {
240       callSender = null;
241     }
242   }
243 
244   // protected for write UT.
245   protected void setupConnection() throws IOException {
246     short ioFailures = 0;
247     short timeoutFailures = 0;
248     while (true) {
249       try {
250         this.socket = this.rpcClient.socketFactory.createSocket();
251         this.socket.setTcpNoDelay(this.rpcClient.isTcpNoDelay());
252         this.socket.setKeepAlive(this.rpcClient.tcpKeepAlive);
253         if (this.rpcClient.localAddr != null) {
254           this.socket.bind(this.rpcClient.localAddr);
255         }
256         NetUtils.connect(this.socket, remoteId.getAddress(), this.rpcClient.connectTO);
257         this.socket.setSoTimeout(this.rpcClient.readTO);
258         return;
259       } catch (SocketTimeoutException toe) {
260         /*
261          * The max number of retries is 45, which amounts to 20s*45 = 15 minutes retries.
262          */
263         handleConnectionFailure(timeoutFailures++, this.rpcClient.maxRetries, toe);
264       } catch (IOException ie) {
265         handleConnectionFailure(ioFailures++, this.rpcClient.maxRetries, ie);
266       }
267     }
268   }
269 
270   /**
271    * Handle connection failures If the current number of retries is equal to the max number of
272    * retries, stop retrying and throw the exception; Otherwise backoff N seconds and try connecting
273    * again. This Method is only called from inside setupIOstreams(), which is synchronized. Hence
274    * the sleep is synchronized; the locks will be retained.
275    * @param curRetries current number of retries
276    * @param maxRetries max number of retries allowed
277    * @param ioe failure reason
278    * @throws IOException if max number of retries is reached
279    */
280   private void handleConnectionFailure(int curRetries, int maxRetries, IOException ioe)
281       throws IOException {
282     closeSocket();
283 
284     // throw the exception if the maximum number of retries is reached
285     if (curRetries >= maxRetries || ExceptionUtil.isInterrupt(ioe)) {
286       throw ioe;
287     }
288 
289     // otherwise back off and retry
290     try {
291       Thread.sleep(this.rpcClient.failureSleep);
292     } catch (InterruptedException ie) {
293       ExceptionUtil.rethrowIfInterrupt(ie);
294     }
295 
296     LOG.info("Retrying connect to server: " + remoteId.getAddress() + " after sleeping " +
297         this.rpcClient.failureSleep + "ms. Already tried " + curRetries + " time(s).");
298   }
299 
300   /*
301    * wait till someone signals us to start reading RPC response or it is idle too long, it is marked
302    * as to be closed, or the client is marked as not running.
303    * @return true if it is time to read a response; false otherwise.
304    */
305   private synchronized boolean waitForWork() {
306     // beware of the concurrent access to the calls list: we can add calls, but as well
307     // remove them.
308     long waitUntil = EnvironmentEdgeManager.currentTime() + this.rpcClient.minIdleTimeBeforeClose;
309     for (;;) {
310       if (thread == null) {
311         return false;
312       }
313       if (!calls.isEmpty()) {
314         return true;
315       }
316       if (EnvironmentEdgeManager.currentTime() >= waitUntil) {
317         closeConn(
318           new IOException("idle connection closed with " + calls.size() + " pending request(s)"));
319         return false;
320       }
321       try {
322         wait(Math.min(this.rpcClient.minIdleTimeBeforeClose, 1000));
323       } catch (InterruptedException e) {
324       }
325     }
326   }
327 
328   @Override
329   public void run() {
330     if (LOG.isTraceEnabled()) {
331       LOG.trace(threadName + ": starting, connections " + this.rpcClient.connections.size());
332     }
333     while (waitForWork()) {
334       readResponse();
335     }
336     if (LOG.isTraceEnabled()) {
337       LOG.trace(threadName + ": stopped, connections " + this.rpcClient.connections.size());
338     }
339   }
340 
341   private void disposeSasl() {
342     if (saslRpcClient != null) {
343       saslRpcClient.dispose();
344       saslRpcClient = null;
345     }
346   }
347 
348   private boolean setupSaslConnection(final InputStream in2, final OutputStream out2)
349       throws IOException {
350     saslRpcClient = new HBaseSaslRpcClient(authMethod, token, serverPrincipal,
351         this.rpcClient.fallbackAllowed, this.rpcClient.conf.get("hbase.rpc.protection",
352           QualityOfProtection.AUTHENTICATION.name().toLowerCase(Locale.ROOT)));
353     return saslRpcClient.saslConnect(in2, out2);
354   }
355 
356   /**
357    * If multiple clients with the same principal try to connect to the same server at the same time,
358    * the server assumes a replay attack is in progress. This is a feature of kerberos. In order to
359    * work around this, what is done is that the client backs off randomly and tries to initiate the
360    * connection again. The other problem is to do with ticket expiry. To handle that, a relogin is
361    * attempted.
362    * <p>
363    * The retry logic is governed by the {@link #shouldAuthenticateOverKrb} method. In case when the
364    * user doesn't have valid credentials, we don't need to retry (from cache or ticket). In such
365    * cases, it is prudent to throw a runtime exception when we receive a SaslException from the
366    * underlying authentication implementation, so there is no retry from other high level (for eg,
367    * HCM or HBaseAdmin).
368    * </p>
369    */
370   private void handleSaslConnectionFailure(final int currRetries, final int maxRetries,
371       final Exception ex, final UserGroupInformation user)
372       throws IOException, InterruptedException {
373     closeSocket();
374     user.doAs(new PrivilegedExceptionAction<Object>() {
375       @Override
376       public Object run() throws IOException, InterruptedException {
377         if (shouldAuthenticateOverKrb()) {
378           if (currRetries < maxRetries) {
379             if (LOG.isDebugEnabled()) {
380               LOG.debug("Exception encountered while connecting to " + "the server : " + ex);
381             }
382             // try re-login
383             relogin();
384             disposeSasl();
385             // have granularity of milliseconds
386             // we are sleeping with the Connection lock held but since this
387             // connection instance is being used for connecting to the server
388             // in question, it is okay
389             Thread.sleep(ThreadLocalRandom.current().nextInt(reloginMaxBackoff) + 1);
390             return null;
391           } else {
392             String msg = "Couldn't setup connection for " +
393                 UserGroupInformation.getLoginUser().getUserName() + " to " + serverPrincipal;
394             LOG.warn(msg, ex);
395             throw (IOException) new IOException(msg).initCause(ex);
396           }
397         } else {
398           LOG.warn("Exception encountered while connecting to " + "the server : " + ex);
399         }
400         if (ex instanceof RemoteException) {
401           throw (RemoteException) ex;
402         }
403         if (ex instanceof SaslException) {
404           String msg = "SASL authentication failed." +
405               " The most likely cause is missing or invalid credentials." + " Consider 'kinit'.";
406           LOG.fatal(msg, ex);
407           throw new RuntimeException(msg, ex);
408         }
409         throw new IOException(ex);
410       }
411     });
412   }
413 
414   private void setupIOstreams() throws IOException {
415     if (socket != null) {
416       // The connection is already available. Perfect.
417       return;
418     }
419 
420     if (this.rpcClient.failedServers.isFailedServer(remoteId.getAddress())) {
421       if (LOG.isDebugEnabled()) {
422         LOG.debug("Not trying to connect to " + remoteId.address +
423             " this server is in the failed servers list");
424       }
425       throw new FailedServerException(
426           "This server is in the failed servers list: " + remoteId.address);
427     }
428 
429     try {
430       if (LOG.isDebugEnabled()) {
431         LOG.debug("Connecting to " + remoteId.address);
432       }
433 
434       short numRetries = 0;
435       final short MAX_RETRIES = 5;
436       while (true) {
437         setupConnection();
438         InputStream inStream = NetUtils.getInputStream(socket);
439         // This creates a socket with a write timeout. This timeout cannot be changed.
440         OutputStream outStream = NetUtils.getOutputStream(socket, this.rpcClient.writeTO);
441         // Write out the preamble -- MAGIC, version, and auth to use.
442         writeConnectionHeaderPreamble(outStream);
443         if (useSasl) {
444           final InputStream in2 = inStream;
445           final OutputStream out2 = outStream;
446           UserGroupInformation ticket = getUGI();
447           boolean continueSasl;
448           if (ticket == null) {
449             throw new FatalConnectionException("ticket/user is null");
450           }
451           try {
452             continueSasl = ticket.doAs(new PrivilegedExceptionAction<Boolean>() {
453               @Override
454               public Boolean run() throws IOException {
455                 return setupSaslConnection(in2, out2);
456               }
457             });
458           } catch (Exception ex) {
459             ExceptionUtil.rethrowIfInterrupt(ex);
460             handleSaslConnectionFailure(numRetries++, MAX_RETRIES, ex, ticket);
461             continue;
462           }
463           if (continueSasl) {
464             // Sasl connect is successful. Let's set up Sasl i/o streams.
465             inStream = saslRpcClient.getInputStream(inStream);
466             outStream = saslRpcClient.getOutputStream(outStream);
467           } else {
468             // fall back to simple auth because server told us so.
469             // do not change authMethod and useSasl here, we should start from secure when
470             // reconnecting because regionserver may change its sasl config after restart.
471           }
472         }
473         this.in = new DataInputStream(new BufferedInputStream(inStream));
474         this.out = new DataOutputStream(new BufferedOutputStream(outStream));
475         // Now write out the connection header
476         writeConnectionHeader();
477         break;
478       }
479     } catch (Throwable t) {
480       closeSocket();
481       IOException e = ExceptionUtil.asInterrupt(t);
482       if (e == null) {
483         this.rpcClient.failedServers.addToFailedServers(remoteId.address, t);
484         if (t instanceof LinkageError) {
485           // probably the hbase hadoop version does not match the running hadoop version
486           e = new DoNotRetryIOException(t);
487         } else if (t instanceof IOException) {
488           e = (IOException) t;
489         } else {
490           e = new IOException("Could not set up IO Streams to " + remoteId.address, t);
491         }
492       }
493       throw e;
494     }
495 
496     // start the receiver thread after the socket connection has been set up
497     thread = new Thread(this, threadName);
498     thread.setDaemon(true);
499     thread.start();
500   }
501 
502   /**
503    * Write the RPC header: {@code <MAGIC WORD -- 'HBas'> <ONEBYTE_VERSION> <ONEBYTE_AUTH_TYPE>}
504    */
505   private void writeConnectionHeaderPreamble(OutputStream out) throws IOException {
506     out.write(connectionHeaderPreamble);
507     out.flush();
508   }
509 
510   /**
511    * Write the connection header.
512    */
513   private void writeConnectionHeader() throws IOException {
514     this.out.write(connectionHeaderWithLength);
515     this.out.flush();
516   }
517 
518   private void tracedWriteRequest(Call call) throws IOException {
519     try (TraceScope ignored = Trace.startSpan("RpcClientImpl.tracedWriteRequest", call.span)) {
520       writeRequest(call);
521     }
522   }
523 
524   /**
525    * Initiates a call by sending the parameter to the remote server. Note: this is not called from
526    * the Connection thread, but by other threads.
527    * @see #readResponse()
528    */
529   private void writeRequest(Call call) throws IOException {
530     ByteBuffer cellBlock =
531         this.rpcClient.cellBlockBuilder.buildCellBlock(this.codec, this.compressor, call.cells);
532     CellBlockMeta cellBlockMeta;
533     if (cellBlock != null) {
534       cellBlockMeta = CellBlockMeta.newBuilder().setLength(cellBlock.limit()).build();
535     } else {
536       cellBlockMeta = null;
537     }
538     RequestHeader requestHeader = buildRequestHeader(call, cellBlockMeta);
539 
540     setupIOstreams();
541 
542     // Now we're going to write the call. We take the lock, then check that the connection
543     // is still valid, and, if so we do the write to the socket. If the write fails, we don't
544     // know where we stand, we have to close the connection.
545     if (Thread.interrupted()) {
546       throw new InterruptedIOException();
547     }
548 
549     calls.put(call.id, call); // We put first as we don't want the connection to become idle.
550     // from here, we do not throw any exception to upper layer as the call has been tracked in the
551     // pending calls map.
552     try {
553       call.callStats.setRequestSizeBytes(write(this.out, requestHeader, call.param, cellBlock));
554     } catch (Throwable t) {
555       if(LOG.isTraceEnabled()) {
556         LOG.trace("Error while writing call, call_id:" + call.id, t);
557       }
558       IOException e = IPCUtil.toIOE(t);
559       closeConn(e);
560       return;
561     }
562     notifyAll();
563   }
564 
565   /*
566    * Receive a response. Because only one receiver, so no synchronization on in.
567    */
568   private void readResponse() {
569     Call call = null;
570     boolean expectedCall = false;
571     try {
572       // See HBaseServer.Call.setResponse for where we write out the response.
573       // Total size of the response. Unused. But have to read it in anyways.
574       int totalSize = in.readInt();
575 
576       // Read the header
577       ResponseHeader responseHeader = ResponseHeader.parseDelimitedFrom(in);
578       int id = responseHeader.getCallId();
579       call = calls.remove(id); // call.done have to be set before leaving this method
580       expectedCall = (call != null && !call.isDone());
581       if (!expectedCall) {
582         // So we got a response for which we have no corresponding 'call' here on the client-side.
583         // We probably timed out waiting, cleaned up all references, and now the server decides
584         // to return a response. There is nothing we can do w/ the response at this stage. Clean
585         // out the wire of the response so its out of the way and we can get other responses on
586         // this connection.
587         int readSoFar = getTotalSizeWhenWrittenDelimited(responseHeader);
588         int whatIsLeftToRead = totalSize - readSoFar;
589         IOUtils.skipFully(in, whatIsLeftToRead);
590         if (call != null) {
591           call.callStats.setResponseSizeBytes(totalSize);
592           call.callStats
593               .setCallTimeMs(EnvironmentEdgeManager.currentTime() - call.callStats.getStartTime());
594         }
595         return;
596       }
597       if (responseHeader.hasException()) {
598         ExceptionResponse exceptionResponse = responseHeader.getException();
599         RemoteException re = createRemoteException(exceptionResponse);
600         call.setException(re);
601         call.callStats.setResponseSizeBytes(totalSize);
602         call.callStats
603             .setCallTimeMs(EnvironmentEdgeManager.currentTime() - call.callStats.getStartTime());
604         if (isFatalConnectionException(exceptionResponse)) {
605           synchronized (this) {
606             closeConn(re);
607           }
608         }
609       } else {
610         Message value = null;
611         if (call.responseDefaultType != null) {
612           Builder builder = call.responseDefaultType.newBuilderForType();
613           ProtobufUtil.mergeDelimitedFrom(builder, in);
614           value = builder.build();
615         }
616         CellScanner cellBlockScanner = null;
617         if (responseHeader.hasCellBlockMeta()) {
618           int size = responseHeader.getCellBlockMeta().getLength();
619           byte[] cellBlock = new byte[size];
620           IOUtils.readFully(this.in, cellBlock, 0, cellBlock.length);
621           cellBlockScanner = this.rpcClient.cellBlockBuilder.createCellScanner(this.codec,
622             this.compressor, cellBlock);
623         }
624         call.setResponse(value, cellBlockScanner);
625         call.callStats.setResponseSizeBytes(totalSize);
626         call.callStats
627             .setCallTimeMs(EnvironmentEdgeManager.currentTime() - call.callStats.getStartTime());
628       }
629     } catch (IOException e) {
630       if (expectedCall) {
631         call.setException(e);
632       }
633       if (e instanceof SocketTimeoutException) {
634         // Clean up open calls but don't treat this as a fatal condition,
635         // since we expect certain responses to not make it by the specified
636         // {@link ConnectionId#rpcTimeout}.
637         if (LOG.isTraceEnabled()) {
638           LOG.trace("ignored", e);
639         }
640       } else {
641         synchronized (this) {
642           closeConn(e);
643         }
644       }
645     }
646   }
647 
648   @Override
649   protected synchronized void callTimeout(Call call) {
650     // call sender
651     calls.remove(call.id);
652   }
653 
654   // just close socket input and output.
655   private void closeSocket() {
656     IOUtils.closeStream(out);
657     IOUtils.closeStream(in);
658     IOUtils.closeSocket(socket);
659     out = null;
660     in = null;
661     socket = null;
662   }
663 
664   // close socket, reader, and clean up all pending calls.
665   private void closeConn(IOException e) {
666     if (thread == null) {
667       return;
668     }
669     thread.interrupt();
670     thread = null;
671     closeSocket();
672     if (callSender != null) {
673       callSender.cleanup(e);
674     }
675     for (Call call : calls.values()) {
676       call.setException(e);
677     }
678     calls.clear();
679   }
680 
681   // release all resources, the connection will not be used any more.
682   @Override
683   public synchronized void shutdown() {
684     closed = true;
685     if (callSender != null) {
686       callSender.interrupt();
687     }
688     closeConn(new IOException("connection to " + remoteId.address + " closed"));
689   }
690 
691   @Override
692   public void cleanupConnection() {
693     // do nothing
694   }
695 
696   @Override
697   public synchronized void sendRequest(final Call call, HBaseRpcController pcrc)
698       throws IOException {
699     pcrc.notifyOnCancel(new RpcCallback<Object>() {
700 
701       @Override
702       public void run(Object parameter) {
703         setCancelled(call);
704         synchronized (BlockingRpcConnection.this) {
705           if (callSender != null) {
706             callSender.remove(call);
707           } else {
708             calls.remove(call.id);
709           }
710         }
711       }
712     }, new CancellationCallback() {
713 
714       @Override
715       public void run(boolean cancelled) throws IOException {
716         if (cancelled) {
717           setCancelled(call);
718           return;
719         }
720         scheduleTimeoutTask(call);
721         if (callSender != null) {
722           callSender.sendCall(call);
723         } else {
724           tracedWriteRequest(call);
725         }
726       }
727     });
728   }
729 
730   @Override
731   public synchronized boolean isActive() {
732     return thread != null;
733   }
734 }