Skip to content

Remove ExceptionHelper.detailedMessage#45878

Merged
pugnascotia merged 21 commits intoelastic:masterfrom
pugnascotia:19069-remove-exceptionhelper-detailedmessage
Sep 17, 2019
Merged

Remove ExceptionHelper.detailedMessage#45878
pugnascotia merged 21 commits intoelastic:masterfrom
pugnascotia:19069-remove-exceptionhelper-detailedmessage

Conversation

@pugnascotia
Copy link
Copy Markdown
Contributor

Closes #19069.

I'm unsure of my approach here, so more opinions would be welcome. Mostly I replaced calls to ExceptionHelper.detailedMessage with e.getMessage(), which is all well and good but does remove some level of detail. There were a couple of cases where I replaced the method call with a throw, in the hope that code elsewhere will catch it and do the right thing.

@pugnascotia pugnascotia added :Core/Infra/Core Core issues without another label v8.0.0 labels Aug 22, 2019
@pugnascotia pugnascotia requested a review from rjernst August 22, 2019 20:21
@elasticmachine
Copy link
Copy Markdown
Collaborator

Pinging @elastic/es-core-infra

@rjernst
Copy link
Copy Markdown
Member

rjernst commented Aug 22, 2019

I don't think changing these uses to getMessage() solves the problem outlined in #19069. We often wrap exceptions, creating deep chains. By changing to getMessage() we omit even more information than detailedMessage is emitting. The problem is we need the actual stack trace. detailedMessage tries to summarize an exception into a single string that omits stack frames from each cause, summarizing as just the exception class + message for each cause. For something like a NullPointerException we lose all information that makes the exception debuggable (the location where the NPE occurred). I think a better substitute would be ExceptionHelper.stackTrace?

return "failed shard, shard " + routingEntry + ", message [" + message + "], failure [" +
ExceptionsHelper.detailedMessage(failure) + "], markAsStale [" + markAsStale + "]";
return "failed shard, shard " + routingEntry + ", message [" + message + "], markAsStale [" + markAsStale + "], failure ["
+ ExceptionsHelper.stackTrace(failure) + "]";
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the order of fields here so that the stack trace comes last.

components.add("markAsStale [" + markAsStale + "]");
if (failure != null) {
components.add("failure [" + ExceptionsHelper.detailedMessage(failure) + "]");
components.add("failure [" + ExceptionsHelper.stackTrace(failure) + "]");
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also changed the order of elements here, so the stack trace is last.

Copy link
Copy Markdown
Member

@rjernst rjernst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this looks better to me. I left a couple last questions, and would also appreicate @jasontedor's opinion on whether using a full stack trace is always appropriate (IMO compared to the useless/reduced info from detailedmessage stacktrace is better).

builder.append(' ');
}
return builder.append(ExceptionsHelper.detailedMessage(this).trim()).toString();
return builder.append(super.toString().trim()).toString();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't we lose all stack information by just using the exception tostring?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only if whatever's calling ElasticsearchException.toString() isn't also printing the stack trace. The problem with calling ElasticsearchException.stackTrace() here is that it calls .toString() on the exception, so you end up blowing the stack. detailedMessage() didn't do this because it essentially reimplemented Throwable.toString(). Open to ideas here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see now why you are using toString(). I've fine with matching the behavior of Exception.

return Strings.toString(builder);
} catch (Exception e) {
return "{ \"error\" : \"" + ExceptionsHelper.detailedMessage(e) + "\"}";
throw new RuntimeException(e);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a test for this failure case so we know the response is not already too far written to throw/serialize an exception like this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have integration tests in SearchSuggestIT with failure cases, but they don't look related to a failure to serialize content. I'd be happy to add a new test - any ideas how to provoke a failure when serialising a DirectCandidateGeneratorBuilder to XContent?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not exactly sure why we have this toString() implemented as json. But upon looking at this again, I think leaving the test coverage as is will be ok. Mimicking a failure would be quite difficult since the json builder is in memory and not mockable.

Copy link
Copy Markdown
Member

@rjernst rjernst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM once CI passes

assertThat(actualFailure.reason(), equalTo(expectedFailure.reason()));
// Serialising and deserialising an exception seems to remove the "java.base/" part from the stack trace,
// so these string replacements account for this.
assertThat(actualFailure.reason().replace("java.base/", ""), equalTo(expectedFailure.reason().replace("java.base/", "")));
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made this change (and again in 1 other class) to get the test to pass, but I don't like it. I'd appreciate other suggestions.

@@ -368,7 +368,7 @@ private void internalRecoverFromStore(IndexShard indexShard) throws IndexShardRe
files = Arrays.toString(store.directory().listAll());
} catch (Exception inner) {
inner.addSuppressed(e);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems odd that we are adding the original exception suppressed here. I dont' think it is necessary since the proper exception cause will be given in IndexShardRecoveryException below.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I've removed this line.

// Serialising and deserialising an exception seems to remove the "java.base/" part from the stack trace,
// so these string replacements account for this.
assertThat(
actualFailure.reason().replace("java.base/", ""),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we check for a portion of the failure instead of an exact stack trace?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I've reworked both instances of this.

A message in RestoreInProgressAllocationDecider included details about
allocation decisions. If an allocation couldn't happen due to an
exception, this resulted in a stack trace in the middle of the message.
Move the details to the end of the message, which makes it easier to
handle in tests.
Instead of relying on substring matching, parse the response JSON and
check fields explicitly. This uses Jackson, which has been added to the
build dependencies for :qa:smoke-test-http.
This required adding a getting to `VerificationFailure` so that the
`cause` field could be accessed and added as suppressed exceptions
elsewhere.
@pugnascotia
Copy link
Copy Markdown
Contributor Author

@elasticsearch run elasticsearch-ci/1

@pugnascotia
Copy link
Copy Markdown
Contributor Author

@rjernst I've merged in master again, would you mind taking another look?

Copy link
Copy Markdown
Member

@rjernst rjernst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, two last nits

apply plugin: 'elasticsearch.test-with-dependencies'

dependencies {
testCompile("com.fasterxml.jackson.core:jackson-databind:2.8.11")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we normally do this without the parens so it maintains the "dsl" look

client().prepareSearch().setQuery(builder).get();
shardFailures = searchResponse.getShardFailures();
assertThat("Expected shard failures, got none", shardFailures, not(emptyArray()));
} catch (SearchPhaseExecutionException e) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should use expectThrows here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding of this code is that it doesn't always throw an exception, so expectThrows isn't appropriate. It was largely taken from ElasticsearchAssertions#assertFailures(SearchRequestBuilder, RestStatus, Matcher<String>), which for $REASONS didn't work for this specific test with the exception helper changes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I misread the assert as a fail.

@pugnascotia
Copy link
Copy Markdown
Contributor Author

@elasticmachine test this please

@pugnascotia
Copy link
Copy Markdown
Contributor Author

@elasticmachine run elasticsearch-ci/1

@pugnascotia
Copy link
Copy Markdown
Contributor Author

@elasticmachine run elasticsearch-ci/bwc

1 similar comment
@pugnascotia
Copy link
Copy Markdown
Contributor Author

@elasticmachine run elasticsearch-ci/bwc

@pugnascotia
Copy link
Copy Markdown
Contributor Author

@elasticmachine update branch

@pugnascotia
Copy link
Copy Markdown
Contributor Author

@elasticmachine run elasticsearch-ci/2

@pugnascotia pugnascotia merged commit ff9e8c6 into elastic:master Sep 17, 2019
@pugnascotia pugnascotia deleted the 19069-remove-exceptionhelper-detailedmessage branch September 17, 2019 17:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

:Core/Infra/Core Core issues without another label >refactoring v8.0.0-alpha1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove uses of ExceptionHelper.detailedMessage

4 participants