Skip to content
Permalink
Browse files

fix case where errors not bubbling up

  • Loading branch information...
grindlemire committed Jun 27, 2018
1 parent c13704b commit 439259938783d96ca316e1bb7e9b57ab90e22e84
Showing with 95 additions and 9 deletions.
  1. +8 −2 README.md
  2. +30 −4 death.go
  3. +55 −2 death_unix_test.go
  4. +2 −1 pkgPath_test.go
@@ -9,7 +9,8 @@ Use gopkg.in to import death based on your logger.

Version | Go Get URL | source | doc | Notes |
--------|------------|--------|-----|-------|
2.x | [gopkg.in/vrecan/death.v2](https://gopkg.in/vrecan/death.v2)| [source]() | [doc]() | This supports loggers who _do not_ return an error from their `Error` and `Warn` functions like [logrus](https://github.com/sirupsen/logrus)
3.x | [gopkg.in/vrecan/death.v3](https://gopkg.in/vrecan/death.v3)| [source](https://github.com/vrecan/death/tree/v3.0) | [doc](https://godoc.org/gopkg.in/vrecan/death.v3) | This removes the need for an independent logger. By default death will not log but will return an error if all the closers do not properly close. If you want to provide a logger just satisfy the deathlog.Logger interface.
2.x | [gopkg.in/vrecan/death.v2](https://gopkg.in/vrecan/death.v2)| [source](https://github.com/vrecan/death/tree/v2.0) | [doc](https://godoc.org/gopkg.in/vrecan/death.v2) | This supports loggers who _do not_ return an error from their `Error` and `Warn` functions like [logrus](https://github.com/sirupsen/logrus)
1.x | [gopkg.in/vrecan/death.v1](https://gopkg.in/vrecan/death.v1)| [souce](https://github.com/vrecan/death/tree/v1.0) | [doc](https://godoc.org/gopkg.in/vrecan/death.v1) | This supports loggers who _do_ return an error from their `Error` and `Warn` functions like [seelog](https://github.com/cihub/seelog)


@@ -42,6 +43,7 @@ func main() {
package main
import (
"log"
DEATH "github.com/vrecan/death"
SYS "syscall"
"io"
@@ -54,7 +56,11 @@ func main() {
objects = append(objects, &NewType{}) // this will work as long as the type implements a Close method
//when you want to block for shutdown signals
death.WaitForDeath(objects...) // this will finish when a signal of your type is sent to your application
err := death.WaitForDeath(objects...) // this will finish when a signal of your type is sent to your application
if err != nil {
log.Println(err)
os.Exit(1)
}
}
type NewType struct {
@@ -31,6 +31,7 @@ type closer struct {
C io.Closer
Name string
PKGPath string
Err error
}

// NewDeath Create Death with the signals you want to die from.
@@ -105,6 +106,7 @@ func (d *Death) closeInMass(closable ...io.Closer) (err error) {

// wait on channel for notifications.
timer := time.NewTimer(d.timeout)
failedClosers := []closer{}
for {
select {
case <-timer.C:
@@ -118,20 +120,31 @@ func (d *Death) closeInMass(closable ...io.Closer) (err error) {
case closer := <-doneClosers:
delete(sentToClose, closer.Index)
count--
if closer.Err != nil {
failedClosers = append(failedClosers, closer)
}

d.log.Debug(count, " object(s) left")
if count == 0 && len(sentToClose) == 0 {
d.log.Debug("Finished closing objects")
return nil
if count != 0 || len(sentToClose) != 0 {
continue
}

if len(failedClosers) != 0 {
errString := generateErrString(failedClosers)
return fmt.Errorf("errors from closers: %s", errString)
}

return nil
}
}
}

// closeObjects and return a bool when finished on a channel.
func (d *Death) closeObjects(closer closer, done chan<- closer) {
err := closer.C.Close()
if nil != err {
if err != nil {
d.log.Error(err)
closer.Err = err
}
done <- closer
}
@@ -156,3 +169,16 @@ func (d *Death) listenForSignal() {
}
}
}

// generateErrString generates a string containing a list of tuples of pkgname to error message
func generateErrString(failedClosers []closer) (errString string) {
for i, fc := range failedClosers {
if i == 0 {
errString = fmt.Sprintf("%s/%s: %s", fc.PKGPath, fc.Name, fc.Err)
continue
}
errString = fmt.Sprintf("%s, %s/%s: %s", errString, fc.PKGPath, fc.Name, fc.Err)
}

return errString
}
@@ -3,7 +3,7 @@
package death

import (
"errors"
"fmt"
"os"
"syscall"
"testing"
@@ -122,6 +122,51 @@ func TestDeath(t *testing.T) {
So(closeMe.Closed, ShouldEqual, 1)
})

Convey("Validate death errors when closer returns error", t, func() {
death := NewDeath(syscall.SIGHUP)
killMe := &KillMe{}
death.FallOnSword()
err := death.WaitForDeath(killMe)
So(err, ShouldNotBeNil)
})

}

func TestGenerateErrString(t *testing.T) {
Convey("Generate for multiple errors", t, func() {
closers := []closer{
closer{
Err: fmt.Errorf("error 1"),
Name: "foo",
PKGPath: "my/pkg",
},
closer{
Err: fmt.Errorf("error 2"),
Name: "bar",
PKGPath: "my/otherpkg",
},
}

expected := "my/pkg/foo: error 1, my/otherpkg/bar: error 2"
actual := generateErrString(closers)

So(actual, ShouldEqual, expected)
})

Convey("Generate for single error", t, func() {
closers := []closer{
closer{
Err: fmt.Errorf("error 1"),
Name: "foo",
PKGPath: "my/pkg",
},
}

expected := "my/pkg/foo: error 1"
actual := generateErrString(closers)

So(actual, ShouldEqual, expected)
})
}

type MockLogger struct {
@@ -160,11 +205,19 @@ func (n *neverClose) Close() error {
return nil
}

// CloseMe returns nil from close
type CloseMe struct {
Closed int
}

func (c *CloseMe) Close() error {
c.Closed++
return errors.New("I have been closed")
return nil
}

// KillMe returns an error from close
type KillMe struct{}

func (c *KillMe) Close() error {
return fmt.Errorf("error from closer")
}
@@ -1,9 +1,10 @@
package death

import (
"testing"

log "github.com/cihub/seelog"
. "github.com/smartystreets/goconvey/convey"
"testing"
)

func TestGetPkgPath(t *testing.T) {

0 comments on commit 4392599

Please sign in to comment.
You can’t perform that action at this time.