|
36 | 36 | import org.slf4j.Logger; |
37 | 37 | import org.slf4j.LoggerFactory; |
38 | 38 |
|
39 | | -import org.apache.hbase.thirdparty.com.google.common.io.ByteStreams; |
40 | | -import org.apache.hbase.thirdparty.com.google.protobuf.CodedInputStream; |
41 | 39 | import org.apache.hbase.thirdparty.com.google.protobuf.InvalidProtocolBufferException; |
42 | 40 |
|
43 | 41 | import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; |
@@ -358,60 +356,46 @@ protected Compression.Algorithm getValueCompressionAlgorithm() { |
358 | 356 |
|
359 | 357 | @Override |
360 | 358 | protected boolean readNext(Entry entry) throws IOException { |
| 359 | + resetCompression = false; |
361 | 360 | // OriginalPosition might be < 0 on local fs; if so, it is useless to us. |
362 | 361 | long originalPosition = this.inputStream.getPos(); |
363 | 362 | if (trailerPresent && originalPosition > 0 && originalPosition == this.walEditsStopOffset) { |
364 | 363 | LOG.trace("Reached end of expected edits area at offset {}", originalPosition); |
365 | 364 | return false; |
366 | 365 | } |
367 | | - WALKey.Builder builder = WALKey.newBuilder(); |
368 | | - long size = 0; |
369 | 366 | boolean resetPosition = false; |
370 | | - // by default, we should reset the compression when seeking back after reading something |
371 | | - resetCompression = true; |
372 | 367 | try { |
373 | | - long available = -1; |
| 368 | + WALKey walKey; |
374 | 369 | try { |
375 | | - int firstByte = this.inputStream.read(); |
376 | | - if (firstByte == -1) { |
377 | | - throw new EOFException(); |
378 | | - } |
379 | | - size = CodedInputStream.readRawVarint32(firstByte, this.inputStream); |
380 | | - // available may be < 0 on local fs for instance. If so, can't depend on it. |
381 | | - available = this.inputStream.available(); |
382 | | - if (available > 0 && available < size) { |
383 | | - // if we quit here, we have just read the length, no actual data yet, which means we |
384 | | - // haven't put anything into the compression dictionary yet, so when seeking back to the |
385 | | - // last good position, we do not need to reset compression context. |
386 | | - // This is very useful for saving the extra effort for reconstructing the compression |
387 | | - // dictionary, where we need to read from the beginning instead of just seek to the |
388 | | - // position, as DFSInputStream implement the available method, so in most cases we will |
389 | | - // reach here if there are not enough data. |
390 | | - resetCompression = false; |
391 | | - throw new EOFException("Available stream not enough for edit, " |
392 | | - + "inputStream.available()= " + this.inputStream.available() + ", " + "entry size= " |
393 | | - + size + " at offset = " + this.inputStream.getPos()); |
| 370 | + walKey = ProtobufUtil.parseDelimitedFrom(inputStream, WALKey.parser()); |
| 371 | + } catch (InvalidProtocolBufferException e) { |
| 372 | + if (ProtobufUtil.isEOF(e) || isWALTrailer(originalPosition)) { |
| 373 | + // only rethrow EOF if it indicates an EOF, or we have reached the partial WALTrailer |
| 374 | + resetPosition = true; |
| 375 | + throw (EOFException) new EOFException("Invalid PB, EOF? Ignoring; originalPosition=" |
| 376 | + + originalPosition + ", currentPosition=" + this.inputStream.getPos()).initCause(e); |
| 377 | + } else { |
| 378 | + throw e; |
394 | 379 | } |
395 | | - ProtobufUtil.mergeFrom(builder, ByteStreams.limit(this.inputStream, size), (int) size); |
396 | | - } catch (InvalidProtocolBufferException ipbe) { |
397 | | - resetPosition = true; |
398 | | - throw (EOFException) new EOFException("Invalid PB, EOF? Ignoring; originalPosition=" |
399 | | - + originalPosition + ", currentPosition=" + this.inputStream.getPos() + ", messageSize=" |
400 | | - + size + ", currentAvailable=" + available).initCause(ipbe); |
| 380 | + } catch (EOFException e) { |
| 381 | + // append more detailed information |
| 382 | + throw (EOFException) new EOFException("EOF while reading WAL key; originalPosition=" |
| 383 | + + originalPosition + ", currentPosition=" + this.inputStream.getPos()).initCause(e); |
401 | 384 | } |
402 | | - if (!builder.isInitialized()) { |
403 | | - // TODO: not clear if we should try to recover from corrupt PB that looks semi-legit. |
404 | | - // If we can get the KV count, we could, theoretically, try to get next record. |
405 | | - throw new EOFException("Partial PB while reading WAL, " |
406 | | - + "probably an unexpected EOF, ignoring. current offset=" + this.inputStream.getPos()); |
407 | | - } |
408 | | - WALKey walKey = builder.build(); |
409 | 385 | entry.getKey().readFieldsFromPb(walKey, this.byteStringUncompressor); |
410 | 386 | if (!walKey.hasFollowingKvCount() || 0 == walKey.getFollowingKvCount()) { |
411 | 387 | LOG.debug("WALKey has no KVs that follow it; trying the next one. current offset={}", |
412 | 388 | this.inputStream.getPos()); |
413 | 389 | return true; |
414 | 390 | } |
| 391 | + // Starting from here, we will start to read cells, which will change the content in |
| 392 | + // compression dictionary, so if we fail in the below operations, when resetting, we also need |
| 393 | + // to clear the compression context, and read from the beginning to reconstruct the |
| 394 | + // compression dictionary, instead of seeking to the position directly. |
| 395 | + // This is very useful for saving the extra effort for reconstructing the compression |
| 396 | + // dictionary, as DFSInputStream implement the available method, so in most cases we will |
| 397 | + // not reach here if there are not enough data. |
| 398 | + resetCompression = true; |
415 | 399 | int expectedCells = walKey.getFollowingKvCount(); |
416 | 400 | long posBefore = this.inputStream.getPos(); |
417 | 401 | try { |
@@ -490,6 +474,54 @@ private IOException extractHiddenEof(Exception ex) { |
490 | 474 | return null; |
491 | 475 | } |
492 | 476 |
|
| 477 | + /** |
| 478 | + * This is used to determine whether we have already reached the WALTrailer. As the size and magic |
| 479 | + * are at the end of the WAL file, it is possible that these two options are missing while |
| 480 | + * writing, so we will consider there is no trailer. And when we actually reach the WALTrailer, we |
| 481 | + * will try to decode it as WALKey and we will fail but the error could be vary as it is parsing |
| 482 | + * WALTrailer actually. |
| 483 | + * @return whether this is a WALTrailer and we should throw EOF to upper layer the file is done |
| 484 | + */ |
| 485 | + private boolean isWALTrailer(long startPosition) throws IOException { |
| 486 | + // We have nothing in the WALTrailer PB message now so its size is just a int length size and a |
| 487 | + // magic at the end |
| 488 | + int trailerSize = PB_WAL_COMPLETE_MAGIC.length + Bytes.SIZEOF_INT; |
| 489 | + if (fileLength - startPosition >= trailerSize) { |
| 490 | + // We still have more than trailerSize bytes before reaching the EOF so this is not a trailer. |
| 491 | + // We also test for == here because if this is a valid trailer, we can read it while opening |
| 492 | + // the reader so we should not reach here |
| 493 | + return false; |
| 494 | + } |
| 495 | + inputStream.seek(startPosition); |
| 496 | + for (int i = 0; i < 4; i++) { |
| 497 | + int r = inputStream.read(); |
| 498 | + if (r == -1) { |
| 499 | + // we have reached EOF while reading the length, and all bytes read are 0, so we assume this |
| 500 | + // is a partial trailer |
| 501 | + return true; |
| 502 | + } |
| 503 | + if (r != 0) { |
| 504 | + // the length is not 0, should not be a trailer |
| 505 | + return false; |
| 506 | + } |
| 507 | + } |
| 508 | + for (int i = 0; i < PB_WAL_COMPLETE_MAGIC.length; i++) { |
| 509 | + int r = inputStream.read(); |
| 510 | + if (r == -1) { |
| 511 | + // we have reached EOF while reading the magic, and all bytes read are matched, so we assume |
| 512 | + // this is a partial trailer |
| 513 | + return true; |
| 514 | + } |
| 515 | + if (r != (PB_WAL_COMPLETE_MAGIC[i] & 0xFF)) { |
| 516 | + // does not match magic, should not be a trailer |
| 517 | + return false; |
| 518 | + } |
| 519 | + } |
| 520 | + // in fact we should not reach here, as this means the trailer bytes are all matched and |
| 521 | + // complete, then we should not call this method... |
| 522 | + return true; |
| 523 | + } |
| 524 | + |
493 | 525 | @Override |
494 | 526 | protected void seekOnFs(long pos) throws IOException { |
495 | 527 | this.inputStream.seek(pos); |
|
0 commit comments