-
Notifications
You must be signed in to change notification settings - Fork 113
Some changes to address blocking after a node is lost #2
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
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 |
|---|---|---|
|
|
@@ -407,17 +407,19 @@ public void handleIO() throws IOException { | |
| int selected = selector.select(delay); | ||
| Set<SelectionKey> selectedKeys = selector.selectedKeys(); | ||
|
|
||
| if (selectedKeys.isEmpty() && !shutDown) { | ||
| if (selector.selectedKeys().isEmpty() && !shutDown) { | ||
| handleEmptySelects(); | ||
| } else { | ||
| getLogger().debug("Selected %d, selected %d keys", selected, | ||
| selectedKeys.size()); | ||
| selector.selectedKeys().size()); | ||
|
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. we've cached them just 2 lines above, do you think we need to access them fresh all the time - why? |
||
| emptySelects = 0; | ||
|
|
||
| for (SelectionKey sk : selectedKeys) { | ||
|
|
||
| Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); | ||
|
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. what would be the benefit of this approach to the old one?
Author
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. selectedKeys is touched by multiple threads, a foreach loop in java is not thread safe as it modifies the set while it loops, i believe instead of using the iterator methods it's using the poll() underneath the covers and can cause "arbitrary, nondeterministic" behavior and/or ConcurrentModificationExceptions. The iterator is the appropriate way to loop through a selectedKeys set in this scenario. I believe that this is an important change based on best practices, and that it only presents problems under high load and thus is very difficult to chase down. 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 see. this code was in place long time ago, It looks good from a code-review perspective, but I'll do some benchmarking to see if it has a perf impact. |
||
| while (iterator.hasNext()) { | ||
| SelectionKey sk = iterator.next(); | ||
| handleIO(sk); | ||
| iterator.remove(); | ||
| } | ||
| selectedKeys.clear(); | ||
| } | ||
|
|
||
| handleOperationalTasks(); | ||
|
|
@@ -1005,10 +1007,11 @@ public void redistributeOperation(Operation op) { | |
| return; | ||
| } | ||
|
|
||
| // The operation gets redistributed but has never been actually written, | ||
| // it we just straight re-add it without cloning. | ||
| // The operation gets redistributed but has never actually been written, | ||
| // then we just straight re-add it without cloning. | ||
| if (op.getState() == OperationState.WRITE_QUEUED) { | ||
| addOperation(op.getHandlingNode(), op); | ||
| addOperation(((KeyedOperation)op).getKeys().iterator().next(), op); | ||
|
Author
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 was adding the op right back onto the node that it just attempted to redistribute away from, and then letting the op go down into the next set of logic to be cloned and redistributed doubly, instead of adding the operation with it's key to get a new node and then returning. 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. That was actually the intention, that we get it to the node directly. I think the bug here is the missing return statement, the addOperation() call is correct. It should go to the same node, because the logic at this point already took it out of the retry queue where it has been put before (for example if the auth latch for the node doesn't complete).
Author
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. but in the case of a dead node that hasn't been detected yet as dead, wouldn't those writes want to get redistributed, is there a check you can do on the node's current interestedops is OP_CONNECT node.getSk().interestOps() == SelectionKey.OP_CONNECT 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. @sveesible I guess we can remove the 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. removing it would also help because down the code we check for multigets, which would not be covered by your change in the line (just one .next()) call
Author
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. doesn't write mean set, and are there multi-sets? the write op is an instance of SingleKeyedOperation, so the .next() would get the only key At the very least the return statement right? 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. no, WRITE_QUEUED means its has been in the write queue for that node before it got redistributed, not tied to any specific operation type. it could be a single or multi key operation (like the multiget below).
Author
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. agreed |
||
| return; | ||
| } | ||
|
|
||
| if (op instanceof MultiGetOperationImpl) { | ||
|
|
@@ -1120,7 +1123,7 @@ private void potentiallyCloseLeakingChannel(final SocketChannel ch, | |
| final MemcachedNode node) { | ||
| if (ch != null && !ch.isConnected() && !ch.isConnectionPending()) { | ||
| try { | ||
| ch.close(); | ||
| ch.socket().close(); | ||
|
Author
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 petty, keeping consistent with queueReconnect closing the socket 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 see.. looking at it in more detail, it could be that the other code is overly verbose, since the ch is a SocketChannel, and I guess if the channel gets closed the underlying socket will be too?
Author
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 think that in certain cases with selectors involved the ch.close() will wait until the next select operation on the channel, but I'm not sure if when it's in a bad state any more selects will be allowed to happen 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. Not sure this will provide any real benefit. Let's get the other things in order first and if we see a specific reason to change this lets do it afterwards. I checked the code and we have both styles in certain places. |
||
| } catch (IOException e) { | ||
| getLogger().error("Exception closing channel: %s", node, e); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -154,7 +154,7 @@ protected Collection<? extends Operation> cloneGet(KeyedOperation op) { | |
| if(getCb != null) { | ||
| rv.add(get(k, getCb)); | ||
| } else if(getsCb != null) { | ||
| rv.add(get(k, getCb)); | ||
| rv.add(gets(k, getsCb)); | ||
|
Author
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 seems like the big one in my testing that clogs up the mechanism when a server node is lost suddenly
Author
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. Please see this change set where the bug was introduced last July 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, that looks like a typo/bug. 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. |
||
| } else { | ||
| rv.add(replicaGet(k, ((ReplicaGetOperationImpl)op).getReplicaIndex() ,replicaGetCb)); | ||
| } | ||
|
|
||
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.
we've cached them just 2 lines above, do you think we need to access them fresh all the time - why?