Giter Site home page Giter Site logo

emperror / errors Goto Github PK

View Code? Open in Web Editor NEW
192.0 4.0 13.0 181 KB

Drop-in replacement for the standard library errors package and github.com/pkg/errors

Home Page: https://emperror.dev/errors

License: MIT License

Go 95.67% Starlark 1.81% Shell 2.51%
error errors stacktrace

errors's Introduction

Emperror: Errors Mentioned in Awesome Go

GitHub Workflow Status Codecov Go Report Card Go Version PkgGoDev FOSSA Status

Drop-in replacement for the standard library errors package and github.com/pkg/errors.

This is a single, lightweight library merging the features of standard library errors package and github.com/pkg/errors. It also backports a few features (like Go 1.13 error handling related features).

Standard library features:

  • New creates an error with stack trace
  • Unwrap supports both Go 1.13 wrapper (interface { Unwrap() error }) and pkg/errors causer (interface { Cause() error }) interface
  • Backported Is and As functions

github.com/pkg/errors features:

  • New, Errorf, WithMessage, WithMessagef, WithStack, Wrap, Wrapf functions behave the same way as in the original library
  • Cause supports both Go 1.13 wrapper (interface { Unwrap() error }) and pkg/errors causer (interface { Cause() error }) interface

Additional features:

  • NewPlain creates a new error without any attached context, like stack trace
  • Sentinel is a shorthand type for creating constant error
  • WithStackDepth allows attaching stack trace with a custom caller depth
  • WithStackDepthIf, WithStackIf, WrapIf, WrapIff only annotate errors with a stack trace if there isn't one already in the error chain
  • Multi error aggregating multiple errors into a single value
  • NewWithDetails, WithDetails and Wrap*WithDetails functions to add key-value pairs to an error
  • Match errors using the match package

Installation

go get emperror.dev/errors

Usage

package main

import "emperror.dev/errors"

// ErrSomethingWentWrong is a sentinel error which can be useful within a single API layer.
const ErrSomethingWentWrong = errors.Sentinel("something went wrong")

// ErrMyError is an error that can be returned from a public API.
type ErrMyError struct {
	Msg string
}

func (e ErrMyError) Error() string {
	return e.Msg
}

func foo() error {
	// Attach stack trace to the sentinel error.
	return errors.WithStack(ErrSomethingWentWrong)
}

func bar() error {
	return errors.Wrap(ErrMyError{"something went wrong"}, "error")
}

func main() {
	if err := foo(); err != nil {
		if errors.Cause(err) == ErrSomethingWentWrong { // or errors.Is(ErrSomethingWentWrong)
			// handle error
		}
	}

	if err := bar(); err != nil {
		if errors.As(err, &ErrMyError{}) {
			// handle error
		}
	}
}

Match errors:

package main

import (
    "emperror.dev/errors"
    "emperror.dev/errors/match"
)

// ErrSomethingWentWrong is a sentinel error which can be useful within a single API layer.
const ErrSomethingWentWrong = errors.Sentinel("something went wrong")

type clientError interface{
    ClientError() bool
}

func foo() error {
	// Attach stack trace to the sentinel error.
	return errors.WithStack(ErrSomethingWentWrong)
}

func main() {
    var ce clientError
    matcher := match.Any{match.As(&ce), match.Is(ErrSomethingWentWrong)}

	if err := foo(); err != nil {
		if matcher.MatchError(err) {
			// you can use matchers to write complex conditions for handling (or not) an error
            // used in emperror
		}
	}
}

Development

Contributions are welcome! :)

  1. Clone the repository
  2. Make changes on a new branch
  3. Run the test suite:
    ./pleasew build
    ./pleasew test
    ./pleasew gotest
    ./pleasew lint
  4. Commit, push and open a PR

License

The MIT License (MIT). Please see License File for more information.

Certain parts of this library are inspired by (or entirely copied from) various third party libraries. Their licenses can be found in the Third Party License File.

FOSSA Status

errors's People

Contributors

sagikazarmark avatar sthaha avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

errors's Issues

match.As not equivalent errors.As

package main

import (
	"fmt"

	"emperror.dev/errors"
	"emperror.dev/errors/match"
)

type (
	myErrorKind string
	MyError     struct {
		kind  myErrorKind
		cause error
	}
)

func (e MyError) Error() string {
	return fmt.Sprintf("%s: %+v", e.kind, e.cause)
}

func main() {
	var (
		err1 error = MyError{
			kind:  "my type",
			cause: errors.Sentinel("some error"),
		}
		targetTrueErrors MyError
		targetTrueMatch  MyError
	)

	fromErrorsTrue := errors.As(err1, &targetTrueErrors)
	fromMatchTrue := match.As(&targetTrueMatch).MatchError(err1)

	fmt.Println("Expecting true:")
	fmt.Printf("  From errors: %t\n", fromErrorsTrue)
	fmt.Printf("  From match : %t\n", fromMatchTrue)

	var (
		err2              error = errors.Sentinel("some error")
		targetFalseErrors MyError
		targetFalseMatch  MyError
	)

	fromErrorsFalse := errors.As(err2, &targetFalseErrors)
	fromMatchFalse := match.As(&targetFalseMatch).MatchError(err2)

	fmt.Println("Expecting false:")
	fmt.Printf("  From errors: %t\n", fromErrorsFalse)
	fmt.Printf("  From match : %t\n", fromMatchFalse)
}

// Output:
// Expecting true:
//  From errors: true
//  From match : true
// Expecting false:
//  From errors: false
//  From match : true

I suspect the problem is at this line, but since I'm in a hurry I haven't tried to solve the problem yet

error.callers holds on to more memory than needed

We were analysing an OOM case and found that errors.callers is holding on to more memory than needed.
E.g.

Given this convoluted testcase
package main

import (

	// pkgerrs "github.com/pkg/errors"
	pkgerrs "emperror.dev/errors"
	"github.com/pkg/profile"
)

var all = []error{}

// go:noinline
func foobarbaz() {
	foobar()

}

// go:noinline
func foobar() {
	foo()
}

// go:noinline
func foo() {
	moo()
}

// go:noinline
func moo() {
	mootoo()
}

// go:noinline
func mootoo() {
	all = append(all, pkgerrs.New("foo"))
}

func main() {
	defer profile.Start(
		profile.MemProfile,
		profile.ProfilePath("."),
	).Stop()

	count := 10_000_000
	for i := 0; i < count; i++ {
		foobarbaz()
	}
}
❯ go run main.go && go tool pprof mem.pprof
2022/02/23 10:20:33 profile: memory profiling enabled (rate 4096), mem.pprof
(pprof) top 3
Showing nodes accounting for 1172.78MB, 95.29% of 1230.71MB total; Dropped 17 nodes (cum <= 6.15MB)
Showing top 3 nodes out of 11
      flat  flat%   sum%        cum   cum%
 1020.92MB 82.95% 82.95%  1020.92MB 82.95%  emperror.dev/errors.callers
   87.18MB  7.08% 90.04%  1108.10MB 90.04%  emperror.dev/errors.WithStackDepth (inline)
   64.68MB  5.26% 95.29%  1230.67MB   100%  main.mootoo

(pprof) list emperror.dev/errors.callers
Total: 1.20GB
ROUTINE ======================== emperror.dev/errors.callers in memleaks/vendor/emperror.dev/errors/stack.go
 1020.92MB  1020.92MB (flat, cum) 82.95% of Total
         .          .     56:func callers(depth int) *stack {
         .          .     57:   const maxDepth = 32
         .          .     58:
  933.54MB   933.54MB     59:   var pcs [maxDepth]uintptr
         .          .     60:
         .          .     61:   n := runtime.Callers(2+depth, pcs[:])
         .          .     62:
   87.39MB    87.39MB     63:   var st stack = pcs[0:n]
         .          .     64:
         .          .     65:   return &st
         .          .     66:}

Here you can see that pcs escapes to heap which is confirmed by

❯ go build -gcflags='-m -m' vendor/emperror.dev/errors/stack.go 2>&1 | grep pcs
vendor/emperror.dev/errors/stack.go:59:6: pcs escapes to heap:
vendor/emperror.dev/errors/stack.go:59:6:   flow: st = &pcs:
vendor/emperror.dev/errors/stack.go:59:6:     from pcs (address-of) at vendor/emperror.dev/errors/stack.go:63:20
vendor/emperror.dev/errors/stack.go:59:6:     from pcs[0:n] (slice) at vendor/emperror.dev/errors/stack.go:63:20
vendor/emperror.dev/errors/stack.go:59:6:     from st = pcs[0:n] (assign) at vendor/emperror.dev/errors/stack.go:63:6
vendor/emperror.dev/errors/stack.go:59:6: moved to heap: pcs

This leads to an error holding onto maxDepth = 32 uintptr than n . The fix is to ensure that pcs doesn't escape to heap which can be achieved by the following.

func callers(depth int) *stack {
	const maxDepth = 32

	var pcs [maxDepth]uintptr

	n := runtime.Callers(2+depth, pcs[:])
	st := make(stack, n)
	copy(st, pcs[:n])

	return &st
}

With this change the pprof for the testcase above shows a deduction of 75%.

(pprof) top 3
Showing nodes accounting for 599.08MB, 90.21% of 664.12MB total
Dropped 16 nodes (cum <= 3.32MB)
Showing top 3 nodes out of 11
      flat  flat%   sum%        cum   cum%
  355.93MB 53.59% 53.59%   355.93MB 53.59%  emperror.dev/errors.callers
  145.53MB 21.91% 75.51%   664.08MB   100%  main.mootoo
   97.62MB 14.70% 90.21%   453.55MB 68.29%  emperror.dev/errors.WithStackDepth (inline)
(pprof) list emperror.dev/errors.callers
Total: 664.12MB
ROUTINE ======================== emperror.dev/errors.callers in memleaks/vendor/emperror.dev/errors/stack.go
  355.93MB   355.93MB (flat, cum) 53.59% of Total
         .          .     57:   const maxDepth = 32
         .          .     58:
         .          .     59:   var pcs [maxDepth]uintptr
         .          .     60:
         .          .     61:   n := runtime.Callers(2+depth, pcs[:])
  355.93MB   355.93MB     62:   st := make(stack, n)
         .          .     63:   copy(st, pcs[:n])
         .          .     64:
         .          .     65:   return &st
         .          .     66:}

Add a marker error

Add an error that allows an error to be marked with a tag/label.

For example:

errors.Mark(err, "clientError")
errors.IsMarked(err, "clientError")

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.