-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
feat(pubsub): add helper method for parsing ErrorInfos #6281
Changes from 23 commits
fb01253
6327975
17d96f9
9f58224
06f21cb
c170640
9f5510c
9b6917c
acdd6c4
ae6d493
8ab1005
ca947ae
1ac3a20
f212ab9
f6262ed
4ab7345
fa7b992
414b2c0
876dfbe
931c1f4
10c8d93
f6c707a
3c97632
7cd8bc5
31963a0
02ac348
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 |
---|---|---|
|
@@ -17,6 +17,7 @@ package pubsub | |
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"strings" | ||
"sync" | ||
|
@@ -27,6 +28,7 @@ import ( | |
"cloud.google.com/go/pubsub/internal/distribution" | ||
gax "github.com/googleapis/gax-go/v2" | ||
pb "google.golang.org/genproto/googleapis/pubsub/v1" | ||
"google.golang.org/genproto/googleapis/rpc/errdetails" | ||
"google.golang.org/grpc" | ||
"google.golang.org/grpc/codes" | ||
"google.golang.org/grpc/status" | ||
|
@@ -662,3 +664,70 @@ func maxDuration(x, y time.Duration) time.Duration { | |
} | ||
return y | ||
} | ||
|
||
func getStatus(err error) *status.Status { | ||
st, ok := status.FromError(err) | ||
if !ok { | ||
return nil | ||
} | ||
return st | ||
} | ||
|
||
// getAckErrors retrieves the metadata of an rpc error if available. | ||
func getAckErrors(err error) map[string]string { | ||
st := getStatus(err) | ||
if st != nil { | ||
for _, detail := range st.Details() { | ||
info, _ := detail.(*errdetails.ErrorInfo) | ||
return info.GetMetadata() | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// processResults processes AckResults by referring to errorStatus and errorsMap. | ||
// The errors returned by the server in `errorStatus` or in `errorsMap` | ||
// are used to complete the AckResults in `ackResMap` (with a success | ||
// or error) or to return requests for further retries. | ||
// Logic is derived from python-pubsub: https://github.com/googleapis/python-pubsub/blob/main/google/cloud/pubsub_v1/subscriber/_protocol/streaming_pull_manager.py#L161-L220 | ||
func processResults(errorStatus *status.Status, ackResMap map[string]*AckResult, errorsMap map[string]string) ([]*AckResult, []*AckResult) { | ||
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. Should errorMap be map[string]error? Seems like you return some of these back to error. Would be good to perserve the orginal error as it could be a part of an error wrapping chain. Also maybe errorsByAckID? 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. Along the same lines I wonder if errorStatus should be an error as it gets "re-cast" back to an error but potentially loses some details in the process 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 changed the map's name and value here to your suggestion to avoid the extra string conversion. I think 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. Hm, actually I was wrong. The map should be |
||
var completedResults, retryResults []*AckResult | ||
for ackID, res := range ackResMap { | ||
// Handle special errors returned for ack/modack RPCs via the ErrorInfo | ||
// sidecar metadata when exactly-once delivery is enabled. | ||
if exactlyOnceErrStr, ok := errorsMap[ackID]; ok { | ||
if strings.HasPrefix(exactlyOnceErrStr, "TRANSIENT_") { | ||
hongalex marked this conversation as resolved.
Show resolved
Hide resolved
|
||
retryResults = append(retryResults, res) | ||
} else { | ||
exactlyOnceErr := fmt.Errorf(exactlyOnceErrStr) | ||
if exactlyOnceErrStr == "PERMANENT_FAILURE_INVALID_ACK_ID" { | ||
ipubsub.SetAckResult(res, AcknowledgeStatusInvalidAckID, exactlyOnceErr) | ||
} else { | ||
ipubsub.SetAckResult(res, AcknowledgeStatusOther, exactlyOnceErr) | ||
} | ||
completedResults = append(completedResults, res) | ||
} | ||
} else if errorStatus != nil && contains(errorStatus.Code(), exactlyOnceDeliveryTemporaryRetryErrors) { | ||
retryResults = append(retryResults, ackResMap[ackID]) | ||
} else if errorStatus != nil { | ||
// Other gRPC errors are not retried. | ||
switch errorStatus.Code() { | ||
case codes.PermissionDenied: | ||
ipubsub.SetAckResult(res, AcknowledgeStatusPermissionDenied, errorStatus.Err()) | ||
case codes.FailedPrecondition: | ||
ipubsub.SetAckResult(res, AcknowledgeStatusFailedPrecondition, errorStatus.Err()) | ||
default: | ||
ipubsub.SetAckResult(res, AcknowledgeStatusOther, errorStatus.Err()) | ||
} | ||
completedResults = append(completedResults, res) | ||
} else if res != nil { | ||
// Since no error occurred, requests with AckResults are completed successfully. | ||
ipubsub.SetAckResult(res, AcknowledgeStatusSuccess, nil) | ||
completedResults = append(completedResults, res) | ||
} else { | ||
// All other requests are considered completed. | ||
completedResults = append(completedResults, res) | ||
} | ||
} | ||
return completedResults, retryResults | ||
} |
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.
If for what you are doing it is easier to work with the raw grpc error this is fine, but all of our error should be of type APIError which might be easier to work with, although I did not look too close here to see if it is a good match.
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.
Actually since we're not using it now, I'll remove it. I'll need it in a later PR.