001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.ipc; 019 020import io.opentelemetry.api.trace.Span; 021import io.opentelemetry.api.trace.StatusCode; 022import io.opentelemetry.context.Scope; 023import java.io.IOException; 024import java.net.InetAddress; 025import java.nio.ByteBuffer; 026import java.security.cert.X509Certificate; 027import java.util.ArrayList; 028import java.util.Collections; 029import java.util.List; 030import java.util.Map; 031import java.util.Optional; 032import java.util.concurrent.atomic.AtomicInteger; 033import org.apache.hadoop.hbase.DoNotRetryIOException; 034import org.apache.hadoop.hbase.ExtendedCellScanner; 035import org.apache.hadoop.hbase.HBaseServerException; 036import org.apache.hadoop.hbase.exceptions.RegionMovedException; 037import org.apache.hadoop.hbase.io.ByteBuffAllocator; 038import org.apache.hadoop.hbase.io.ByteBufferListOutputStream; 039import org.apache.hadoop.hbase.ipc.RpcServer.CallCleanup; 040import org.apache.hadoop.hbase.security.User; 041import org.apache.hadoop.hbase.trace.TraceUtil; 042import org.apache.hadoop.hbase.util.ByteBufferUtils; 043import org.apache.hadoop.hbase.util.Bytes; 044import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 045import org.apache.hadoop.util.StringUtils; 046import org.apache.yetus.audience.InterfaceAudience; 047 048import org.apache.hbase.thirdparty.com.google.common.collect.Maps; 049import org.apache.hbase.thirdparty.com.google.protobuf.BlockingService; 050import org.apache.hbase.thirdparty.com.google.protobuf.CodedOutputStream; 051import org.apache.hbase.thirdparty.com.google.protobuf.Descriptors.MethodDescriptor; 052import org.apache.hbase.thirdparty.com.google.protobuf.Message; 053 054import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 055import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; 056import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.VersionInfo; 057import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.CellBlockMeta; 058import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.ExceptionResponse; 059import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.RequestHeader; 060import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.ResponseHeader; 061 062/** 063 * Datastructure that holds all necessary to a method invocation and then afterward, carries the 064 * result. 065 */ 066@InterfaceAudience.Private 067public abstract class ServerCall<T extends ServerRpcConnection> implements RpcCall, RpcResponse { 068 069 protected final int id; // the client's call id 070 protected final BlockingService service; 071 protected final MethodDescriptor md; 072 protected final RequestHeader header; 073 protected Message param; // the parameter passed 074 // Optional cell data passed outside of protobufs. 075 protected final ExtendedCellScanner cellScanner; 076 protected final T connection; // connection to client 077 protected final long receiveTime; // the time received when response is null 078 // the time served when response is not null 079 protected final int timeout; 080 protected long startTime; 081 protected final long deadline;// the deadline to handle this call, if exceed we can drop it. 082 083 protected final ByteBuffAllocator bbAllocator; 084 085 protected final CellBlockBuilder cellBlockBuilder; 086 087 /** 088 * Chain of buffers to send as response. 089 */ 090 protected BufferChain response; 091 092 protected final long size; // size of current call 093 protected boolean isError; 094 protected ByteBufferListOutputStream cellBlockStream = null; 095 protected CallCleanup reqCleanup = null; 096 097 protected final User user; 098 protected final InetAddress remoteAddress; 099 protected final X509Certificate[] clientCertificateChain; 100 protected RpcCallback rpcCallback; 101 102 private long responseCellSize = 0; 103 private long responseBlockSize = 0; 104 // cumulative size of serialized exceptions 105 private long exceptionSize = 0; 106 private final boolean retryImmediatelySupported; 107 private volatile Map<String, byte[]> requestAttributes; 108 109 // This is a dirty hack to address HBASE-22539. The highest bit is for rpc ref and cleanup, and 110 // the rest of the bits are for WAL reference count. We can only call release if all of them are 111 // zero. The reason why we can not use a general reference counting is that, we may call cleanup 112 // multiple times in the current implementation. We should fix this in the future. 113 // The refCount here will start as 0x80000000 and increment with every WAL reference and decrement 114 // from WAL side on release 115 private final AtomicInteger reference = new AtomicInteger(0x80000000); 116 117 private final Span span; 118 119 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "NP_NULL_ON_SOME_PATH", 120 justification = "Can't figure why this complaint is happening... see below") 121 ServerCall(int id, BlockingService service, MethodDescriptor md, RequestHeader header, 122 Message param, ExtendedCellScanner cellScanner, T connection, long size, 123 InetAddress remoteAddress, long receiveTime, int timeout, ByteBuffAllocator byteBuffAllocator, 124 CellBlockBuilder cellBlockBuilder, CallCleanup reqCleanup) { 125 this.id = id; 126 this.service = service; 127 this.md = md; 128 this.header = header; 129 this.param = param; 130 this.cellScanner = cellScanner; 131 this.connection = connection; 132 this.receiveTime = receiveTime; 133 this.response = null; 134 this.isError = false; 135 this.size = size; 136 if (connection != null) { 137 this.user = connection.user; 138 this.retryImmediatelySupported = connection.retryImmediatelySupported; 139 this.clientCertificateChain = connection.clientCertificateChain; 140 } else { 141 this.user = null; 142 this.retryImmediatelySupported = false; 143 this.clientCertificateChain = null; 144 } 145 this.remoteAddress = remoteAddress; 146 this.timeout = timeout; 147 this.deadline = this.timeout > 0 ? this.receiveTime + this.timeout : Long.MAX_VALUE; 148 this.bbAllocator = byteBuffAllocator; 149 this.cellBlockBuilder = cellBlockBuilder; 150 this.reqCleanup = reqCleanup; 151 this.span = Span.current(); 152 } 153 154 /** 155 * Call is done. Execution happened and we returned results to client. It is now safe to cleanup. 156 */ 157 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "IS2_INCONSISTENT_SYNC", 158 justification = "Presume the lock on processing request held by caller is protection enough") 159 @Override 160 public void done() { 161 if (this.cellBlockStream != null) { 162 // This will return back the BBs which we got from pool. 163 this.cellBlockStream.releaseResources(); 164 this.cellBlockStream = null; 165 } 166 // If the call was run successfuly, we might have already returned the BB 167 // back to pool. No worries..Then inputCellBlock will be null 168 cleanup(); 169 span.end(); 170 } 171 172 @Override 173 public void cleanup() { 174 for (;;) { 175 int ref = reference.get(); 176 if ((ref & 0x80000000) == 0) { 177 return; 178 } 179 int nextRef = ref & 0x7fffffff; 180 if (reference.compareAndSet(ref, nextRef)) { 181 if (nextRef == 0) { 182 if (this.reqCleanup != null) { 183 this.reqCleanup.run(); 184 } 185 } 186 return; 187 } 188 } 189 } 190 191 public void retainByWAL() { 192 reference.incrementAndGet(); 193 } 194 195 public void releaseByWAL() { 196 // Here this method of decrementAndGet for releasing WAL reference count will work in both 197 // cases - i.e. highest bit (cleanup) 1 or 0. We will be decrementing a negative or positive 198 // value respectively in these 2 cases, but the logic will work the same way 199 if (reference.decrementAndGet() == 0) { 200 if (this.reqCleanup != null) { 201 this.reqCleanup.run(); 202 } 203 } 204 205 } 206 207 @Override 208 public String toString() { 209 return toShortString() + " param: " 210 + (this.param != null ? ProtobufUtil.getShortTextFormat(this.param) : "") + " connection: " 211 + connection.toString(); 212 } 213 214 @Override 215 public RequestHeader getHeader() { 216 return this.header; 217 } 218 219 @Override 220 public Map<String, byte[]> getConnectionAttributes() { 221 return this.connection.connectionAttributes; 222 } 223 224 @Override 225 public Map<String, byte[]> getRequestAttributes() { 226 if (this.requestAttributes == null) { 227 if (header.getAttributeList().isEmpty()) { 228 this.requestAttributes = Collections.emptyMap(); 229 } else { 230 Map<String, byte[]> requestAttributes = 231 Maps.newHashMapWithExpectedSize(header.getAttributeList().size()); 232 for (HBaseProtos.NameBytesPair nameBytesPair : header.getAttributeList()) { 233 requestAttributes.put(nameBytesPair.getName(), nameBytesPair.getValue().toByteArray()); 234 } 235 this.requestAttributes = requestAttributes; 236 } 237 } 238 return this.requestAttributes; 239 } 240 241 @Override 242 public byte[] getRequestAttribute(String key) { 243 if (this.requestAttributes == null) { 244 for (HBaseProtos.NameBytesPair nameBytesPair : header.getAttributeList()) { 245 if (nameBytesPair.getName().equals(key)) { 246 return nameBytesPair.getValue().toByteArray(); 247 } 248 } 249 return null; 250 } 251 return this.requestAttributes.get(key); 252 } 253 254 @Override 255 public int getPriority() { 256 return this.header.getPriority(); 257 } 258 259 /** 260 * Get the ServerRpcConnection associated with this call. 261 * @return the connection object 262 */ 263 public T getConnection() { 264 return this.connection; 265 } 266 267 /* 268 * Short string representation without param info because param itself could be huge depends on 269 * the payload of a command 270 */ 271 @Override 272 public String toShortString() { 273 String serviceName = this.connection.service != null 274 ? this.connection.service.getDescriptorForType().getName() 275 : "null"; 276 return "callId: " + this.id + " service: " + serviceName + " methodName: " 277 + ((this.md != null) ? this.md.getName() : "n/a") + " size: " 278 + StringUtils.TraditionalBinaryPrefix.long2String(this.size, "", 1) + " connection: " 279 + connection + " deadline: " + deadline; 280 } 281 282 @Override 283 public synchronized void setResponse(Message m, final ExtendedCellScanner cells, Throwable t, 284 String errorMsg) { 285 if (this.isError) { 286 return; 287 } 288 if (t != null) { 289 this.isError = true; 290 TraceUtil.setError(span, t); 291 } else { 292 span.setStatus(StatusCode.OK); 293 } 294 BufferChain bc = null; 295 try { 296 ResponseHeader.Builder headerBuilder = ResponseHeader.newBuilder(); 297 // Call id. 298 headerBuilder.setCallId(this.id); 299 if (t != null) { 300 setExceptionResponse(t, errorMsg, headerBuilder); 301 } 302 // Pass reservoir to buildCellBlock. Keep reference to returne so can add it back to the 303 // reservoir when finished. This is hacky and the hack is not contained but benefits are 304 // high when we can avoid a big buffer allocation on each rpc. 305 List<ByteBuffer> cellBlock = null; 306 int cellBlockSize = 0; 307 if (bbAllocator.isReservoirEnabled()) { 308 this.cellBlockStream = this.cellBlockBuilder.buildCellBlockStream(this.connection.codec, 309 this.connection.compressionCodec, cells, bbAllocator); 310 if (this.cellBlockStream != null) { 311 cellBlock = this.cellBlockStream.getByteBuffers(); 312 cellBlockSize = this.cellBlockStream.size(); 313 } 314 } else { 315 ByteBuffer b = this.cellBlockBuilder.buildCellBlock(this.connection.codec, 316 this.connection.compressionCodec, cells); 317 if (b != null) { 318 cellBlockSize = b.remaining(); 319 cellBlock = new ArrayList<>(1); 320 cellBlock.add(b); 321 } 322 } 323 324 if (cellBlockSize > 0) { 325 CellBlockMeta.Builder cellBlockBuilder = CellBlockMeta.newBuilder(); 326 // Presumes the cellBlock bytebuffer has been flipped so limit has total size in it. 327 cellBlockBuilder.setLength(cellBlockSize); 328 headerBuilder.setCellBlockMeta(cellBlockBuilder.build()); 329 } 330 Message header = headerBuilder.build(); 331 ByteBuffer headerBuf = createHeaderAndMessageBytes(m, header, cellBlockSize, cellBlock); 332 ByteBuffer[] responseBufs = null; 333 int cellBlockBufferSize = 0; 334 if (cellBlock != null) { 335 cellBlockBufferSize = cellBlock.size(); 336 responseBufs = new ByteBuffer[1 + cellBlockBufferSize]; 337 } else { 338 responseBufs = new ByteBuffer[1]; 339 } 340 responseBufs[0] = headerBuf; 341 if (cellBlock != null) { 342 for (int i = 0; i < cellBlockBufferSize; i++) { 343 responseBufs[i + 1] = cellBlock.get(i); 344 } 345 } 346 bc = new BufferChain(responseBufs); 347 } catch (IOException e) { 348 RpcServer.LOG.warn("Exception while creating response " + e); 349 bc = createFallbackErrorResponse(e); 350 } 351 this.response = bc; 352 // Once a response message is created and set to this.response, this Call can be treated as 353 // done. The Responder thread will do the n/w write of this message back to client. 354 if (this.rpcCallback != null) { 355 try (Scope ignored = span.makeCurrent()) { 356 this.rpcCallback.run(); 357 } catch (Exception e) { 358 // Don't allow any exception here to kill this handler thread. 359 RpcServer.LOG.warn("Exception while running the Rpc Callback.", e); 360 TraceUtil.setError(span, e); 361 } 362 } 363 } 364 365 static void setExceptionResponse(Throwable t, String errorMsg, 366 ResponseHeader.Builder headerBuilder) { 367 ExceptionResponse.Builder exceptionBuilder = ExceptionResponse.newBuilder(); 368 exceptionBuilder.setExceptionClassName(t.getClass().getName()); 369 exceptionBuilder.setStackTrace(errorMsg); 370 exceptionBuilder.setDoNotRetry(t instanceof DoNotRetryIOException); 371 if (t instanceof RegionMovedException) { 372 // Special casing for this exception. This is only one carrying a payload. 373 // Do this instead of build a generic system for allowing exceptions carry 374 // any kind of payload. 375 RegionMovedException rme = (RegionMovedException) t; 376 exceptionBuilder.setHostname(rme.getHostname()); 377 exceptionBuilder.setPort(rme.getPort()); 378 } else if (t instanceof HBaseServerException) { 379 HBaseServerException hse = (HBaseServerException) t; 380 exceptionBuilder.setServerOverloaded(hse.isServerOverloaded()); 381 } 382 // Set the exception as the result of the method invocation. 383 headerBuilder.setException(exceptionBuilder.build()); 384 } 385 386 /* 387 * Creates a fallback error response when the primary response creation fails. This method is 388 * invoked as a last resort when an IOException occurs during the normal response creation 389 * process. It attempts to create a minimal error response containing only the error information, 390 * without any cell blocks or additional data. The purpose is to ensure that the client receives 391 * some indication of the failure rather than experiencing a silent connection drop. This provides 392 * better error handling on the client side. 393 */ 394 private BufferChain createFallbackErrorResponse(IOException originalException) { 395 try { 396 ResponseHeader.Builder headerBuilder = ResponseHeader.newBuilder(); 397 headerBuilder.setCallId(this.id); 398 String responseErrorMsg = 399 "Failed to create response due to: " + originalException.getMessage(); 400 setExceptionResponse(originalException, responseErrorMsg, headerBuilder); 401 Message header = headerBuilder.build(); 402 ByteBuffer headerBuf = createHeaderAndMessageBytes(null, header, 0, null); 403 this.isError = true; 404 return new BufferChain(new ByteBuffer[] { headerBuf }); 405 } catch (IOException e) { 406 RpcServer.LOG.error("Failed to create error response for client, connection may be dropped", 407 e); 408 return null; 409 } 410 } 411 412 static ByteBuffer createHeaderAndMessageBytes(Message result, Message header, int cellBlockSize, 413 List<ByteBuffer> cellBlock) throws IOException { 414 // Organize the response as a set of bytebuffers rather than collect it all together inside 415 // one big byte array; save on allocations. 416 // for writing the header, we check if there is available space in the buffers 417 // created for the cellblock itself. If there is space for the header, we reuse 418 // the last buffer in the cellblock. This applies to the cellblock created from the 419 // pool or even the onheap cellblock buffer in case there is no pool enabled. 420 // Possible reuse would avoid creating a temporary array for storing the header every time. 421 ByteBuffer possiblePBBuf = (cellBlockSize > 0) ? cellBlock.get(cellBlock.size() - 1) : null; 422 int headerSerializedSize = 0, resultSerializedSize = 0, headerVintSize = 0, resultVintSize = 0; 423 if (header != null) { 424 headerSerializedSize = header.getSerializedSize(); 425 headerVintSize = CodedOutputStream.computeUInt32SizeNoTag(headerSerializedSize); 426 } 427 if (result != null) { 428 resultSerializedSize = result.getSerializedSize(); 429 resultVintSize = CodedOutputStream.computeUInt32SizeNoTag(resultSerializedSize); 430 } 431 // calculate the total size 432 int totalSize = headerSerializedSize + headerVintSize + (resultSerializedSize + resultVintSize) 433 + cellBlockSize; 434 int totalPBSize = headerSerializedSize + headerVintSize + resultSerializedSize + resultVintSize 435 + Bytes.SIZEOF_INT; 436 // Only if the last buffer has enough space for header use it. Else allocate 437 // a new buffer. Assume they are all flipped 438 if (possiblePBBuf != null && possiblePBBuf.limit() + totalPBSize <= possiblePBBuf.capacity()) { 439 // duplicate the buffer. This is where the header is going to be written 440 ByteBuffer pbBuf = possiblePBBuf.duplicate(); 441 // get the current limit 442 int limit = pbBuf.limit(); 443 // Position such that we write the header to the end of the buffer 444 pbBuf.position(limit); 445 // limit to the header size 446 pbBuf.limit(totalPBSize + limit); 447 // mark the current position 448 pbBuf.mark(); 449 writeToCOS(result, header, totalSize, pbBuf); 450 // reset the buffer back to old position 451 pbBuf.reset(); 452 return pbBuf; 453 } else { 454 return createHeaderAndMessageBytes(result, header, totalSize, totalPBSize); 455 } 456 } 457 458 private static void writeToCOS(Message result, Message header, int totalSize, ByteBuffer pbBuf) 459 throws IOException { 460 ByteBufferUtils.putInt(pbBuf, totalSize); 461 // create COS that works on BB 462 CodedOutputStream cos = CodedOutputStream.newInstance(pbBuf); 463 if (header != null) { 464 cos.writeMessageNoTag(header); 465 } 466 if (result != null) { 467 cos.writeMessageNoTag(result); 468 } 469 cos.flush(); 470 cos.checkNoSpaceLeft(); 471 } 472 473 private static ByteBuffer createHeaderAndMessageBytes(Message result, Message header, 474 int totalSize, int totalPBSize) throws IOException { 475 ByteBuffer pbBuf = ByteBuffer.allocate(totalPBSize); 476 writeToCOS(result, header, totalSize, pbBuf); 477 pbBuf.flip(); 478 return pbBuf; 479 } 480 481 @Override 482 public long disconnectSince() { 483 if (!this.connection.isConnectionOpen()) { 484 return EnvironmentEdgeManager.currentTime() - receiveTime; 485 } else { 486 return -1L; 487 } 488 } 489 490 @Override 491 public boolean isClientCellBlockSupported() { 492 return this.connection != null && this.connection.codec != null; 493 } 494 495 @Override 496 public long getResponseCellSize() { 497 return responseCellSize; 498 } 499 500 @Override 501 public void incrementResponseCellSize(long cellSize) { 502 responseCellSize += cellSize; 503 } 504 505 @Override 506 public long getBlockBytesScanned() { 507 return responseBlockSize; 508 } 509 510 @Override 511 public void incrementBlockBytesScanned(long blockSize) { 512 responseBlockSize += blockSize; 513 } 514 515 @Override 516 public long getResponseExceptionSize() { 517 return exceptionSize; 518 } 519 520 @Override 521 public void incrementResponseExceptionSize(long exSize) { 522 exceptionSize += exSize; 523 } 524 525 @Override 526 public long getSize() { 527 return this.size; 528 } 529 530 @Override 531 public long getDeadline() { 532 return deadline; 533 } 534 535 @Override 536 public Optional<User> getRequestUser() { 537 return Optional.ofNullable(user); 538 } 539 540 @Override 541 public Optional<X509Certificate[]> getClientCertificateChain() { 542 return Optional.ofNullable(clientCertificateChain); 543 } 544 545 @Override 546 public InetAddress getRemoteAddress() { 547 return remoteAddress; 548 } 549 550 @Override 551 public VersionInfo getClientVersionInfo() { 552 return connection.getVersionInfo(); 553 } 554 555 @Override 556 public synchronized void setCallBack(RpcCallback callback) { 557 this.rpcCallback = callback; 558 } 559 560 @Override 561 public boolean isRetryImmediatelySupported() { 562 return retryImmediatelySupported; 563 } 564 565 @Override 566 public BlockingService getService() { 567 return service; 568 } 569 570 @Override 571 public MethodDescriptor getMethod() { 572 return md; 573 } 574 575 @Override 576 public Message getParam() { 577 return param; 578 } 579 580 @Override 581 public ExtendedCellScanner getCellScanner() { 582 return cellScanner; 583 } 584 585 @Override 586 public long getReceiveTime() { 587 return receiveTime; 588 } 589 590 @Override 591 public long getStartTime() { 592 return startTime; 593 } 594 595 @Override 596 public void setStartTime(long t) { 597 this.startTime = t; 598 } 599 600 @Override 601 public int getTimeout() { 602 return timeout; 603 } 604 605 @Override 606 public int getRemotePort() { 607 return connection.getRemotePort(); 608 } 609 610 @Override 611 public synchronized BufferChain getResponse() { 612 return response; 613 } 614}