-
Notifications
You must be signed in to change notification settings - Fork 3.4k
HBASE-22539 WAL corruption due to early DBBs re-use when Durability.A… #437
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,7 +23,7 @@ | |
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
import java.util.concurrent.atomic.AtomicInteger; | ||
import org.apache.hadoop.hbase.CellScanner; | ||
import org.apache.hadoop.hbase.DoNotRetryIOException; | ||
import org.apache.hadoop.hbase.io.ByteBuffAllocator; | ||
|
@@ -51,7 +51,7 @@ | |
* the result. | ||
*/ | ||
@InterfaceAudience.Private | ||
abstract class ServerCall<T extends ServerRpcConnection> implements RpcCall, RpcResponse { | ||
public abstract class ServerCall<T extends ServerRpcConnection> implements RpcCall, RpcResponse { | ||
|
||
protected final int id; // the client's call id | ||
protected final BlockingService service; | ||
|
@@ -91,6 +91,12 @@ abstract class ServerCall<T extends ServerRpcConnection> implements RpcCall, Rpc | |
private long exceptionSize = 0; | ||
private final boolean retryImmediatelySupported; | ||
|
||
// This is a dirty hack to address HBASE-22539. The lowest bit is for normal rpc cleanup, and the | ||
// second bit is for WAL reference. We can only call release if both of them are zero. The reason | ||
// why we can not use a general reference counting is that, we may call cleanup multiple times in | ||
// the current implementation. We should fix this in the future. | ||
private final AtomicInteger reference = new AtomicInteger(0b01); | ||
|
||
@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "NP_NULL_ON_SOME_PATH", | ||
justification = "Can't figure why this complaint is happening... see below") | ||
ServerCall(int id, BlockingService service, MethodDescriptor md, RequestHeader header, | ||
|
@@ -141,14 +147,43 @@ public void done() { | |
cleanup(); | ||
} | ||
|
||
private void release(int mask) { | ||
for (;;) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should rather wait for notifications from cleanup()? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I can not get your point. What's the problem here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I meant to put a wait call inside this for block, so that it does not keep iterating indefinitely. But I guess condition on #153 will already halt it on the 2nd iteration, so should be fine. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is for CAS. You can see the code in AtomicInteger.getAndUpdate, almost the same. |
||
int ref = reference.get(); | ||
if ((ref & mask) == 0) { | ||
return; | ||
} | ||
int nextRef = ref & (~mask); | ||
if (reference.compareAndSet(ref, nextRef)) { | ||
if (nextRef == 0) { | ||
if (this.reqCleanup != null) { | ||
this.reqCleanup.run(); | ||
} | ||
} | ||
return; | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So here we rely that cleanup() will never be called before retainByWal(), otherwise the rpc call may end before the wal write is done? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we should call retainByWal before cleanup. So we must call retainByWal in the rpc handler thread, before putting it into the ringbuffer of WAL, I think this is enough to make sure that we call retainByWal before cleanup. |
||
public void cleanup() { | ||
if (this.reqCleanup != null) { | ||
this.reqCleanup.run(); | ||
this.reqCleanup = null; | ||
release(0b01); | ||
} | ||
|
||
public void retainByWAL() { | ||
for (;;) { | ||
wchevreuil marked this conversation as resolved.
Show resolved
Hide resolved
|
||
int ref = reference.get(); | ||
int nextRef = ref | 0b10; | ||
if (reference.compareAndSet(ref, nextRef)) { | ||
return; | ||
} | ||
} | ||
} | ||
|
||
public void releaseByWAL() { | ||
release(0b10); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return toShortString() + " param: " + | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -59,6 +59,8 @@ | |
import org.apache.hadoop.hbase.client.RegionInfo; | ||
import org.apache.hadoop.hbase.exceptions.TimeoutIOException; | ||
import org.apache.hadoop.hbase.io.util.MemorySizeUtil; | ||
import org.apache.hadoop.hbase.ipc.RpcServer; | ||
import org.apache.hadoop.hbase.ipc.ServerCall; | ||
import org.apache.hadoop.hbase.log.HBaseMarkers; | ||
import org.apache.hadoop.hbase.regionserver.MultiVersionConcurrencyControl; | ||
import org.apache.hadoop.hbase.trace.TraceUtil; | ||
|
@@ -971,7 +973,7 @@ boolean isUnflushedEntries() { | |
* Exposed for testing only. Use to tricks like halt the ring buffer appending. | ||
*/ | ||
@VisibleForTesting | ||
void atHeadOfRingBufferEventHandlerAppend() { | ||
protected void atHeadOfRingBufferEventHandlerAppend() { | ||
// Noop | ||
} | ||
|
||
|
@@ -1061,8 +1063,10 @@ protected final long stampSequenceIdAndPublishToRingBuffer(RegionInfo hri, WALKe | |
txidHolder.setValue(ringBuffer.next()); | ||
}); | ||
long txid = txidHolder.longValue(); | ||
ServerCall<?> rpcCall = RpcServer.getCurrentCall().filter(c -> c instanceof ServerCall) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The currentCall should always be a ServerCall I think, so no need the extra instanceof ? It's also OK, if you think it's fine. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just for safety. |
||
.filter(c -> c.getCellScanner() != null).map(c -> (ServerCall) c).orElse(null); | ||
try (TraceScope scope = TraceUtil.createTrace(implClassName + ".append")) { | ||
FSWALEntry entry = new FSWALEntry(txid, key, edits, hri, inMemstore); | ||
FSWALEntry entry = new FSWALEntry(txid, key, edits, hri, inMemstore, rpcCall); | ||
entry.stampRegionSequenceId(we); | ||
ringBuffer.get(txid).load(entry); | ||
} finally { | ||
|
Uh oh!
There was an error while loading. Please reload this page.