api/errors: Improve performance of Is* functions

Type assertion is faster than "errors.As" when the error is not wrapped.
Allocations are unaffected.

Benchmark results:
```
name                                    old time/op    new time/op    delta
IsAlreadyExistsWrappedErrors/Nil-4        72.8ns ±11%    79.6ns ±12%     ~     (p=0.343 n=4+4)
IsAlreadyExistsWrappedErrors/Bare-4        336ns ± 6%      95ns ±10%  -71.70%  (p=0.029 n=4+4)
IsAlreadyExistsWrappedErrors/Wrapped-4     416ns ± 1%     426ns ± 4%     ~     (p=0.486 n=4+4)
IsNotFoundWrappedErrors/Nil-4              102ns ± 4%      97ns ± 4%     ~     (p=0.114 n=4+4)
IsNotFoundWrappedErrors/Bare-4             350ns ± 3%     102ns ± 6%  -70.78%  (p=0.029 n=4+4)
IsNotFoundWrappedErrors/Wrapped-4          448ns ± 3%     462ns ± 2%     ~     (p=0.200 n=4+4)
```
This commit is contained in:
Chris Bandy 2022-05-20 22:14:58 -05:00
parent d404991eff
commit 13ebe3b454
2 changed files with 56 additions and 8 deletions

View File

@ -96,8 +96,8 @@ func HasStatusCause(err error, name metav1.CauseType) bool {
// StatusCause returns the named cause from the provided error if it exists and // StatusCause returns the named cause from the provided error if it exists and
// the error unwraps to the type APIStatus. Otherwise it returns false. // the error unwraps to the type APIStatus. Otherwise it returns false.
func StatusCause(err error, name metav1.CauseType) (metav1.StatusCause, bool) { func StatusCause(err error, name metav1.CauseType) (metav1.StatusCause, bool) {
var status APIStatus status, ok := err.(APIStatus)
if errors.As(err, &status) && status.Status().Details != nil { if (ok || errors.As(err, &status)) && status.Status().Details != nil {
for _, cause := range status.Status().Details.Causes { for _, cause := range status.Status().Details.Causes {
if cause.Type == name { if cause.Type == name {
return cause, true return cause, true
@ -757,7 +757,8 @@ func IsRequestEntityTooLargeError(err error) bool {
// and may be the result of another HTTP actor. // and may be the result of another HTTP actor.
// It supports wrapped errors and returns false when the error is nil. // It supports wrapped errors and returns false when the error is nil.
func IsUnexpectedServerError(err error) bool { func IsUnexpectedServerError(err error) bool {
if status := APIStatus(nil); errors.As(err, &status) && status.Status().Details != nil { status, ok := err.(APIStatus)
if (ok || errors.As(err, &status)) && status.Status().Details != nil {
for _, cause := range status.Status().Details.Causes { for _, cause := range status.Status().Details.Causes {
if cause.Type == metav1.CauseTypeUnexpectedServerResponse { if cause.Type == metav1.CauseTypeUnexpectedServerResponse {
return true return true
@ -770,8 +771,8 @@ func IsUnexpectedServerError(err error) bool {
// IsUnexpectedObjectError determines if err is due to an unexpected object from the master. // IsUnexpectedObjectError determines if err is due to an unexpected object from the master.
// It supports wrapped errors and returns false when the error is nil. // It supports wrapped errors and returns false when the error is nil.
func IsUnexpectedObjectError(err error) bool { func IsUnexpectedObjectError(err error) bool {
uoe := &UnexpectedObjectError{} uoe, ok := err.(*UnexpectedObjectError)
return err != nil && errors.As(err, &uoe) return err != nil && (ok || errors.As(err, &uoe))
} }
// SuggestsClientDelay returns true if this error suggests a client delay as well as the // SuggestsClientDelay returns true if this error suggests a client delay as well as the
@ -780,7 +781,8 @@ func IsUnexpectedObjectError(err error) bool {
// request delay without retry. // request delay without retry.
// It supports wrapped errors and returns false when the error is nil. // It supports wrapped errors and returns false when the error is nil.
func SuggestsClientDelay(err error) (int, bool) { func SuggestsClientDelay(err error) (int, bool) {
if t := APIStatus(nil); errors.As(err, &t) && t.Status().Details != nil { t, ok := err.(APIStatus)
if (ok || errors.As(err, &t)) && t.Status().Details != nil {
switch t.Status().Reason { switch t.Status().Reason {
// this StatusReason explicitly requests the caller to delay the action // this StatusReason explicitly requests the caller to delay the action
case metav1.StatusReasonServerTimeout: case metav1.StatusReasonServerTimeout:
@ -798,14 +800,14 @@ func SuggestsClientDelay(err error) (int, bool) {
// It supports wrapped errors and returns StatusReasonUnknown when // It supports wrapped errors and returns StatusReasonUnknown when
// the error is nil or doesn't have a status. // the error is nil or doesn't have a status.
func ReasonForError(err error) metav1.StatusReason { func ReasonForError(err error) metav1.StatusReason {
if status := APIStatus(nil); errors.As(err, &status) { if status, ok := err.(APIStatus); ok || errors.As(err, &status) {
return status.Status().Reason return status.Status().Reason
} }
return metav1.StatusReasonUnknown return metav1.StatusReasonUnknown
} }
func reasonAndCodeForError(err error) (metav1.StatusReason, int32) { func reasonAndCodeForError(err error) (metav1.StatusReason, int32) {
if status := APIStatus(nil); errors.As(err, &status) { if status, ok := err.(APIStatus); ok || errors.As(err, &status) {
return status.Status().Reason, status.Status().Code return status.Status().Reason, status.Status().Code
} }
return metav1.StatusReasonUnknown, 0 return metav1.StatusReasonUnknown, 0

View File

@ -657,3 +657,49 @@ func TestStatusCauseSupportsWrappedErrors(t *testing.T) {
t.Errorf("expected cause when nested, got %v: %#v", ok, cause) t.Errorf("expected cause when nested, got %v: %#v", ok, cause)
} }
} }
func BenchmarkIsAlreadyExistsWrappedErrors(b *testing.B) {
err := NewAlreadyExists(schema.GroupResource{}, "")
wrapped := fmt.Errorf("once: %w", err)
b.Run("Nil", func(b *testing.B) {
for i := 0; i < b.N; i++ {
IsAlreadyExists(nil)
}
})
b.Run("Bare", func(b *testing.B) {
for i := 0; i < b.N; i++ {
IsAlreadyExists(err)
}
})
b.Run("Wrapped", func(b *testing.B) {
for i := 0; i < b.N; i++ {
IsAlreadyExists(wrapped)
}
})
}
func BenchmarkIsNotFoundWrappedErrors(b *testing.B) {
err := NewNotFound(schema.GroupResource{}, "")
wrapped := fmt.Errorf("once: %w", err)
b.Run("Nil", func(b *testing.B) {
for i := 0; i < b.N; i++ {
IsNotFound(nil)
}
})
b.Run("Bare", func(b *testing.B) {
for i := 0; i < b.N; i++ {
IsNotFound(err)
}
})
b.Run("Wrapped", func(b *testing.B) {
for i := 0; i < b.N; i++ {
IsNotFound(wrapped)
}
})
}