-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Fix ActionListener.map exception handling #50886
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
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
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.