-
Notifications
You must be signed in to change notification settings - Fork 3.3k
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
HBASE-24962 Optimize BufferNode Lock #2343
base: master
Are you sure you want to change the base?
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 |
---|---|---|
|
@@ -36,6 +36,7 @@ | |
import org.apache.hadoop.hbase.procedure2.util.StringUtils; | ||
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; | ||
import org.apache.hadoop.hbase.util.Threads; | ||
import org.apache.hbase.thirdparty.com.google.common.collect.Sets; | ||
import org.apache.hbase.thirdparty.com.google.common.util.concurrent.ThreadFactoryBuilder; | ||
import org.apache.yetus.audience.InterfaceAudience; | ||
import org.slf4j.Logger; | ||
|
@@ -298,6 +299,8 @@ protected <T extends RemoteOperation> List<T> fetchType( | |
// ============================================================================================ | ||
private final class TimeoutExecutorThread extends Thread { | ||
private final DelayQueue<DelayedWithTimeout> queue = new DelayQueue<DelayedWithTimeout>(); | ||
private final ConcurrentHashMap<DelayedWithTimeout, DelayedWithTimeout> pendingBufferNode = | ||
new ConcurrentHashMap<>(); | ||
|
||
public TimeoutExecutorThread() { | ||
super("ProcedureDispatcherTimeoutThread"); | ||
|
@@ -314,17 +317,26 @@ public void run() { | |
if (task instanceof DelayedTask) { | ||
threadPool.execute(((DelayedTask) task).getObject()); | ||
} else { | ||
pendingBufferNode.remove(task); | ||
((BufferNode) task).dispatch(); | ||
} | ||
} | ||
} | ||
|
||
private void putIfAbsent(BufferNode bufferNode) { | ||
if (pendingBufferNode.putIfAbsent(bufferNode, bufferNode) == null) { | ||
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. If we want to add same object as key and value, why are we using 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. use atomically putIfAbsent. |
||
bufferNode.setTimeout(EnvironmentEdgeManager.currentTime() + operationDelay); | ||
queue.add(bufferNode); | ||
} | ||
} | ||
|
||
public void add(final DelayedWithTimeout delayed) { | ||
queue.add(delayed); | ||
} | ||
|
||
public void remove(final DelayedWithTimeout delayed) { | ||
queue.remove(delayed); | ||
pendingBufferNode.remove(delayed); | ||
} | ||
|
||
public void sendStopSignal() { | ||
|
@@ -357,8 +369,9 @@ public void awaitTermination() { | |
*/ | ||
protected final class BufferNode extends DelayedContainerWithTimestamp<TRemote> | ||
implements RemoteNode<TEnv, TRemote> { | ||
private Set<RemoteProcedure> operations; | ||
private final Set<RemoteProcedure> dispatchedOperations = new HashSet<>(); | ||
private Set<RemoteProcedure> operations = Sets.newConcurrentHashSet(); | ||
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.
e.g
Nothing urgent, can be taken up in separate patch to well distinguish the commit purpose. |
||
private final Set<RemoteProcedure> dispatchedOperations = Sets.newConcurrentHashSet(); | ||
private final Object lock = new Object(); | ||
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. While this looks promising but 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. yeah, but abortOperationsInQueue does't happen very often |
||
|
||
protected BufferNode(final TRemote key) { | ||
super(key, 0); | ||
|
@@ -370,39 +383,47 @@ public TRemote getKey() { | |
} | ||
|
||
@Override | ||
public synchronized void add(final RemoteProcedure operation) { | ||
if (this.operations == null) { | ||
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. Are you sure we are matching this condition with 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. TimeoutExecutorThread#run will remove task from pendingBufferNode, so after run, task will not be included in the pendingBufferNode else { |
||
this.operations = new HashSet<>(); | ||
setTimeout(EnvironmentEdgeManager.currentTime() + operationDelay); | ||
timeoutExecutor.add(this); | ||
} | ||
public void add(final RemoteProcedure operation) { | ||
this.operations.add(operation); | ||
if (this.operations.size() > queueMaxSize) { | ||
timeoutExecutor.remove(this); | ||
dispatch(); | ||
synchronized (lock) { | ||
if (this.operations.size() > queueMaxSize) { | ||
timeoutExecutor.remove(this); | ||
dispatch(); | ||
} | ||
//all procedure have been scheduled by the current thread or another thread. | ||
return; | ||
} | ||
} | ||
timeoutExecutor.putIfAbsent(this); | ||
} | ||
|
||
@Override | ||
public synchronized void dispatch() { | ||
if (operations != null) { | ||
remoteDispatch(getKey(), operations); | ||
operations.stream().filter(operation -> operation.storeInDispatchedQueue()) | ||
.forEach(operation -> dispatchedOperations.add(operation)); | ||
this.operations = null; | ||
public void dispatch() { | ||
Set<RemoteProcedure> operationsTmp = operations; | ||
operations = Sets.newConcurrentHashSet(); | ||
if (operationsTmp.isEmpty()) { | ||
return; | ||
} | ||
remoteDispatch(getKey(), operations); | ||
operations.stream().filter(RemoteProcedure::storeInDispatchedQueue) | ||
.forEach(dispatchedOperations::add); | ||
} | ||
|
||
public synchronized void abortOperationsInQueue() { | ||
if (operations != null) { | ||
abortPendingOperations(getKey(), operations); | ||
this.operations = null; | ||
public void abortOperationsInQueue() { | ||
synchronized (lock) { | ||
if (!operations.isEmpty()) { | ||
abortPendingOperations(getKey(), operations); | ||
operations = Sets.newConcurrentHashSet(); | ||
} | ||
if (!dispatchedOperations.isEmpty()) { | ||
abortPendingOperations(getKey(), dispatchedOperations); | ||
this.dispatchedOperations.clear(); | ||
} | ||
} | ||
abortPendingOperations(getKey(), dispatchedOperations); | ||
this.dispatchedOperations.clear(); | ||
} | ||
|
||
public synchronized void operationCompleted(final RemoteProcedure remoteProcedure){ | ||
public void operationCompleted(final RemoteProcedure remoteProcedure){ | ||
this.dispatchedOperations.remove(remoteProcedure); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: please use ConcurrentMap
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?why use concurrentMap
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a general rule, it's good to handle implementation by using interface to avoid the pain of changing all references if we wish to change the implementation. I understand, we don't want to change implementation here, but it's always great to refer to implementation using interface unless we want to deliberately use implementor methods which are not overridden from interface.
Hence, just like why we would want to use
Map<Key,Val> map=new HashMap<>
, let's also use:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thx @virajjasani
yes, but i think it is okay to use either, RemoteProcedureDispatcher doesn't change often