-
Notifications
You must be signed in to change notification settings - Fork 24.9k
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
Fix ActionListener.map exception handling #50886
Fix ActionListener.map exception handling #50886
Conversation
map would call listener.onFailure for exceptions from listener.onResponse, but this means we could double trigger some listeners which is generally unexpected. Instead, we should assume that a listener's onResponse (and onFailure) implementation is responsible for its own exception handling.
Pinging @elastic/es-core-infra (:Core/Infra/Core) |
Pinging @elastic/es-distributed (:Distributed/Distributed) |
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.
Changes look good.
How do you feel about adding some JavaDoc to the new test cases, to explain what the expectations are?
delegate.onFailure(e); | ||
return; | ||
} | ||
delegate.onResponse(mapped); |
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.
I see the point in this change, but note that I added the .map
shortcut back when I added it to dry up a bunch of ActionListener.wrap(..., listener::onFailure)
spots.
I think we'd basically have to audit every spot that we use .map
in now and make sure that the listener
/delegate
will actually handle it's own onResponse
failures (from a quick read over the spots we use map
in this may already hold true).
Maybe we should assert
this and do something like:
try {
delegate.onResponse(mapped);
} catch (Exception e) {
assert false: e;
throw e;
}
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.
I added the assert. Local CI seems happy about it.
Thanks @pugnascotia , I added javadoc here: 198651b |
The DFS action relied on map notifying onFailure (sort of, at least this way it is bwc). But there seems to be no reason it cannot simply use the ChannelActionListener, so change it into using that.
Failure already reported here. |
Removed WIP on this, since I think this can go in alone. |
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.
I have not gone through all callers of this method to check whether this is safe, but left a few small comments here.
try { | ||
delegate.onResponse(mapped); | ||
} catch (RuntimeException e) { | ||
assert false : new AssertionError("map: listener.onResponse failed", e); |
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 assert here but not when calling delegate.onFailure(e);
?
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.
Thanks, added that in ca31964 and tests seems unaffected.
}); | ||
(request, channel, task) -> | ||
searchService.executeDfsPhase(request, (SearchShardTask) task, | ||
new ChannelActionListener<>(channel, DFS_ACTION_NAME, request)) |
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.
ChannelActionListener
also has this weird double-sending logic.
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.
Yes, but the code used to propagate the exception out. This seemed to be a left-over from when the ChannelActionListener
was introduced. The old map
exception handling would ensure onFailure
were called on exception. This means this change is effectively a no-op now (with the caveat that onFailure
will be called on exceptions like for all other ChannelActionListener
usages).
We should notice that DirectTransportChannel
will not bubble out exceptions from invoking the TransportResponseHandler
. So it seems likely that the primary exceptions bubbled out are related to communicating the response over a wire, in which case invoking onFailure
might be desirable (for instance an NPE on serialization).
So I would like to keep using ChannelActionListener
here and then deal with ChannelActionListener
in a follow-up. WDYT?
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.
++
server/src/test/java/org/elasticsearch/action/admin/indices/create/CreateIndexIT.java
Outdated
Show resolved
Hide resolved
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.
LGTM thanks Henning :)
@elasticmachine test this please |
Build failure looks unrelated, reported it here: #51347 . |
One more test round: |
I went through all calls in production (not test) code. Most are "clearly" OK, falling into one of these categories:
Following I did not follow to the end: PersistentTasksService: this affects all methods here. I checked a subset of the usages only:
The small addendum mentioned previously is that I did not consider the effect of having for instance |
ActionListener.map would call listener.onFailure for exceptions from listener.onResponse, but this means we could double trigger some listeners which is generally unexpected. Instead, we should assume that a listener's onResponse (and onFailure) implementation is responsible for its own exception handling.
This reverts commit 92ee0f7.
Datafeeds being closed while starting could result in and NPE. This was handled as any other failure, masking out the NPE. However, this conflicts with the changes in elastic#50886. Related to elastic#50886 and elastic#51302
ActionListener.map would call listener.onFailure for exceptions from listener.onResponse, but this means we could double trigger some listeners which is generally unexpected. Instead, we should assume that a listener's onResponse (and onFailure) implementation is responsible for its own exception handling.
ActionListener.map would call listener.onFailure for exceptions from listener.onResponse, but this means we could double trigger some listeners which is generally unexpected. Instead, we should assume that a listener's onResponse (and onFailure) implementation is responsible for its own exception handling.
Notice that this PR was merged, then reverted. After #51646 was merged, the commit was cherry-picked (identical, no changes) to reintroduce it. |
ActionListener.map would call listener.onFailure for exceptions from listener.onResponse, but this means we could double trigger some listeners which is generally unexpected. Instead, we should assume that a listener's onResponse (and onFailure) implementation is responsible for its own exception handling.
ActionListener.completeWith would catch exceptions from listener.onResponse and deliver them to lister.onFailure, essentially double notifying the listener. Instead we now assert that listeners do not throw when using ActionListener.completeWith. Relates elastic#50886
ActionListener.completeWith would catch exceptions from listener.onResponse and deliver them to lister.onFailure, essentially double notifying the listener. Instead we now assert that listeners do not throw when using ActionListener.completeWith. Relates #50886
ActionListener.completeWith would catch exceptions from listener.onResponse and deliver them to lister.onFailure, essentially double notifying the listener. Instead we now assert that listeners do not throw when using ActionListener.completeWith. Relates #50886
map would call listener.onFailure for exceptions from
listener.onResponse, but this means we could double trigger some
listeners which is generally unexpected. Instead, we should assume that
a listener's onResponse (and onFailure) implementation is responsible
for its own exception handling.
This affects many APIs across the code base. This is the first of a series
of PRs changing exception handling to adhere to the principle that an
ActionListener implementation is responsible for its own exception handling.
The demo program test here illustrates the current inconsistencies in how exceptions in listeners are handled.