1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
81
82
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
97 protected Socket socket = null;
98 private DataInputStream in;
99 private DataOutputStream out;
100
101 private HBaseSaslRpcClient saslRpcClient;
102
103
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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
157
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
166
167 @Override
168 public void run() {
169 synchronized (BlockingRpcConnection.this) {
170 while (!closed) {
171 if (callsToWrite.isEmpty()) {
172
173
174
175 try {
176 BlockingRpcConnection.this.wait();
177 } catch (InterruptedException e) {
178 }
179
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
190
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
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
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
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
272
273
274
275
276
277
278
279
280 private void handleConnectionFailure(int curRetries, int maxRetries, IOException ioe)
281 throws IOException {
282 closeSocket();
283
284
285 if (curRetries >= maxRetries || ExceptionUtil.isInterrupt(ioe)) {
286 throw ioe;
287 }
288
289
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
302
303
304
305 private synchronized boolean waitForWork() {
306
307
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
358
359
360
361
362
363
364
365
366
367
368
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
383 relogin();
384 disposeSasl();
385
386
387
388
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
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
440 OutputStream outStream = NetUtils.getOutputStream(socket, this.rpcClient.writeTO);
441
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
465 inStream = saslRpcClient.getInputStream(inStream);
466 outStream = saslRpcClient.getOutputStream(outStream);
467 } else {
468
469
470
471 }
472 }
473 this.in = new DataInputStream(new BufferedInputStream(inStream));
474 this.out = new DataOutputStream(new BufferedOutputStream(outStream));
475
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
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
497 thread = new Thread(this, threadName);
498 thread.setDaemon(true);
499 thread.start();
500 }
501
502
503
504
505 private void writeConnectionHeaderPreamble(OutputStream out) throws IOException {
506 out.write(connectionHeaderPreamble);
507 out.flush();
508 }
509
510
511
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
526
527
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
543
544
545 if (Thread.interrupted()) {
546 throw new InterruptedIOException();
547 }
548
549 calls.put(call.id, call);
550
551
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
567
568 private void readResponse() {
569 Call call = null;
570 boolean expectedCall = false;
571 try {
572
573
574 int totalSize = in.readInt();
575
576
577 ResponseHeader responseHeader = ResponseHeader.parseDelimitedFrom(in);
578 int id = responseHeader.getCallId();
579 call = calls.remove(id);
580 expectedCall = (call != null && !call.isDone());
581 if (!expectedCall) {
582
583
584
585
586
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
635
636
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
651 calls.remove(call.id);
652 }
653
654
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
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
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
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 }