Skip to content

feat(grpc): preserve unknown detail types when decoding gRPC errors #5905

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

Merged
merged 1 commit into from
Apr 29, 2025

Conversation

fiam
Copy link
Collaborator

@fiam fiam commented Apr 10, 2025

If decoding the status fails, retain the original proto message by
registering it as a detail.

This change also adds a test to verify that unknown types are preserved when:

  • Decoding a gRPC error with unknown detail types.
  • Re-encoding and decoding the error using ToGRPC / FromGRPC.

Additionally, fix an issue where the codes.Code string prefix was
not handled correctly, resulting in a duplicate prefix.

@fiam fiam force-pushed the alberto/grpc-preserve-unknown-types branch 2 times, most recently from c9db49b to 79dccda Compare April 10, 2025 20:50
@@ -45,7 +46,7 @@ func ToGRPC(ctx context.Context, err error) error {

// If the original error was wrapped with more context than the GRPCStatus error,
// copy the original message to the GRPCStatus error
if err.Error() != st.Message() {
if errorHasMoreContext(err, st) {
Copy link
Member

Choose a reason for hiding this comment

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

What is the case where we don't want the original details (or ones set in FromGRPC) to not be added into this error?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

AsGRPCStatus() unwraps until it finds something implementing GRPCStatus(). So if we had something like errors.Wrap(errors.Wrap(st.Err(), "foo") "bar"), it would discard the foo: bar: prefix otherwise.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

As for the opposite, st.Err().Error() prepends the code to st.Message() so naively setting the message again would duplicate the prefix that corresponds to the codes.Code.

Check this playground https://go.dev/play/p/UKFRlAyVlYT

@fiam fiam force-pushed the alberto/grpc-preserve-unknown-types branch from 79dccda to 8630be0 Compare April 10, 2025 21:23
if errMessage := err.Error(); len(errMessage) > len(st.Message()) {
// check if the longer message in errMessage is only due to
// prepending with the status code
prefix := st.Code().String() + ": "
Copy link
Member

Choose a reason for hiding this comment

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

is this trying to mimic grpcStatusError.Error(). In that case looks like it is more complicated as some codes have special meaning. Type checking to *grpcStatusError could be better.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've updated the check to use *grpcStatusError, let me know if that's what you had in mind. Thanks!

If decoding the status fails, retain the original proto message by
registering it as a detail.

This change also adds a test to verify that unknown types are preserved when:

- Decoding a gRPC error with unknown detail types.
- Re-encoding and decoding the error using ToGRPC / FromGRPC.

Additionally, fix an issue where the codes.Code string prefix was
not handled correctly, resulting in a duplicate prefix.

Signed-off-by: Alberto Garcia Hierro <[email protected]>
@fiam fiam force-pushed the alberto/grpc-preserve-unknown-types branch from 8630be0 to 5848d95 Compare April 15, 2025 12:56
// errorHasMoreContext checks if the original error provides more context by having
// a different message or additional details than the Status.
func errorHasMoreContext(err error, st *status.Status) bool {
if errMessage := err.Error(); len(errMessage) > len(st.Message()) {
Copy link
Member

Choose a reason for hiding this comment

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

what's this length check for?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That's a narrower version of the pre-existing check at https://github.com/moby/buildkit/pull/5905/files#diff-ab206f22f3b8cb9ead9e872d93d8a668f181b0bbdc75f29059a88ebbff1abe13L48 - You don't want to replace the original error message if somehow we ended up with a shorter one, because the objective is to keep the maximum amount of context.

The general idea of that function is that if the original error had a longer string than the status.Status message, we replace the status message with the full error message unless we can prove that the longer error message only comes from prepending the codes.Code.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@tonistiigi ping :-)

@tonistiigi tonistiigi merged commit f198c38 into moby:master Apr 29, 2025
111 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants