Skip to content

Commit

Permalink
Singleton: Allow reset of singleton, makes it possible to reinit an t…
Browse files Browse the repository at this point in the history
…ype (#52)
  • Loading branch information
donutloop committed May 9, 2021
1 parent 65effb8 commit 1b783ea
Show file tree
Hide file tree
Showing 11 changed files with 96 additions and 41 deletions.
2 changes: 1 addition & 1 deletion debugutil/doc_test.go
Expand Up @@ -11,4 +11,4 @@ func ExamplePrettySprint() {
fmt.Println(str)
// Output: []string{
//}
}
}
3 changes: 1 addition & 2 deletions debugutil/http_dump.go
Expand Up @@ -8,7 +8,6 @@ import (
"strings"
)


// PrettyPrintResponse is pretty printing a http response
func PrettySprintResponse(resp *http.Response) (string, error) {
dump, err := PrettyDumpResponse(resp, true)
Expand Down Expand Up @@ -71,4 +70,4 @@ func PrettyDumpRequest(req *http.Request, body bool) ([]byte, error) {
}

return b, nil
}
}
4 changes: 2 additions & 2 deletions debugutil/http_log_round_tripper.go
Expand Up @@ -12,7 +12,7 @@ type logger interface {

type LogRoundTripper struct {
http.RoundTripper
logger logger
logger logger
dumpBody bool
}

Expand Down Expand Up @@ -40,7 +40,7 @@ func (tr LogRoundTripper) RoundTrip(req *http.Request) (res *http.Response, err
if err != nil {
tr.logger.Errorf("could not dump response: %v", err)
} else {
tr.logger.Infof( "------------ HTTP RESPONSE ----------\n%s", responseDump)
tr.logger.Infof("------------ HTTP RESPONSE ----------\n%s", responseDump)
}
}

Expand Down
3 changes: 1 addition & 2 deletions debugutil/http_log_round_tripper_test.go
Expand Up @@ -9,7 +9,7 @@ import (
"testing"
)

type logger struct {}
type logger struct{}

func (l logger) Errorf(format string, v ...interface{}) {
log.Println(fmt.Sprintf(format, v...))
Expand Down Expand Up @@ -42,4 +42,3 @@ func TestLogRoundTripper_RoundTrip(t *testing.T) {
t.Fatal("response is bad, got: $v, want: $v", response.StatusCode, http.StatusOK)
}
}

15 changes: 7 additions & 8 deletions retry/retry.go
Expand Up @@ -35,25 +35,24 @@ func NewRetrier(InitialIntervalInSeconds, maxIntervalInSeconds float64, tries ui

return &retrier{
InitialIntervalInSeconds: InitialIntervalInSeconds,
maxIntervalInSeconds: maxIntervalInSeconds,
strategy: strategy,
tries: tries,
maxIntervalInSeconds: maxIntervalInSeconds,
strategy: strategy,
tries: tries,
}
}

// Retry supervised do funcs which automatically handle failures when they occur by performing retries.
type retrier struct {
InitialIntervalInSeconds, maxIntervalInSeconds float64
strategy Strategy
tries uint
strategy Strategy
tries uint
}

func (r *retrier) Retry(ctx context.Context, do RetryableDo) error {
if ctx == nil {
ctx = context.Background()
}


var err error
var done bool
for i := uint(0); !done && i < r.tries; i++ {
Expand Down Expand Up @@ -82,9 +81,9 @@ type Strategy interface {
Policy(intervalInSeconds, maxIntervalInSeconds float64) float64
}

type Exp struct {}
type Exp struct{}

func (e *Exp) Policy(intervalInSeconds, maxIntervalInSeconds float64) float64 {
time.Sleep(time.Duration(intervalInSeconds) * time.Second)
return math.Min(intervalInSeconds*2, maxIntervalInSeconds)
}
}
14 changes: 7 additions & 7 deletions retry/roundtripper.go
Expand Up @@ -6,17 +6,17 @@ import (
)

type RoundTripper struct {
retrier Retrier
next http.RoundTripper
retrier Retrier
next http.RoundTripper
blacklistStatusCodes []int
}

// NewRoundTripper is constructing a new retry RoundTripper with given default values
func NewRoundTripper(next http.RoundTripper, maxInterval, initialInterval float64, tries uint, blacklistStatusCodes []int, strategy Strategy) *RoundTripper {
retrier := NewRetrier(initialInterval, maxInterval, tries, strategy)
return &RoundTripper{
retrier:retrier,
next: next,
retrier: retrier,
next: next,
blacklistStatusCodes: blacklistStatusCodes,
}
}
Expand All @@ -39,10 +39,10 @@ func (rt *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
return false, nil
}

return true , nil
return true, nil
})

if _, ok := err.(*ExhaustedError); ok {
if _, ok := err.(*ExhaustedError); ok {
return resp, nil
}

Expand All @@ -60,4 +60,4 @@ func (rt *RoundTripper) isStatusCode(statusCode int) bool {
}
}
return false
}
}
28 changes: 13 additions & 15 deletions retry/roundtripper_test.go
Expand Up @@ -13,16 +13,16 @@ func TestRoundTripper_RoundTripInternalServer(t *testing.T) {

var counter int32
testserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
atomic.AddInt32(&counter, 1)
t.Log("hit endpoint")
w.WriteHeader(http.StatusInternalServerError)
atomic.AddInt32(&counter, 1)
t.Log("hit endpoint")
w.WriteHeader(http.StatusInternalServerError)
}))

retryRoundTripper := NewRoundTripper(http.DefaultTransport, .50 , .15 , 3, nil, new(Exp))
retryRoundTripper := NewRoundTripper(http.DefaultTransport, .50, .15, 3, nil, new(Exp))
httpClient := new(http.Client)
httpClient.Transport = retryRoundTripper

req, err := http.NewRequest(http.MethodGet, testserver.URL, nil )
req, err := http.NewRequest(http.MethodGet, testserver.URL, nil)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -50,11 +50,11 @@ func TestRoundTripper_RoundTripInternalServerBlacklisted(t *testing.T) {
w.WriteHeader(http.StatusInternalServerError)
}))

retryRoundTripper := NewRoundTripper(http.DefaultTransport, .50 , .15 , 3, []int{http.StatusInternalServerError}, new(Exp))
retryRoundTripper := NewRoundTripper(http.DefaultTransport, .50, .15, 3, []int{http.StatusInternalServerError}, new(Exp))
httpClient := new(http.Client)
httpClient.Transport = retryRoundTripper

req, err := http.NewRequest(http.MethodGet, testserver.URL, nil )
req, err := http.NewRequest(http.MethodGet, testserver.URL, nil)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -82,11 +82,11 @@ func TestRoundTripper_RoundTripStatusOk(t *testing.T) {
w.WriteHeader(http.StatusOK)
}))

retryRoundTripper := NewRoundTripper(http.DefaultTransport, .50 , .15 , 3, nil, new(Exp))
retryRoundTripper := NewRoundTripper(http.DefaultTransport, .50, .15, 3, nil, new(Exp))
httpClient := new(http.Client)
httpClient.Transport = retryRoundTripper

req, err := http.NewRequest(http.MethodGet, testserver.URL, nil )
req, err := http.NewRequest(http.MethodGet, testserver.URL, nil)
if err != nil {
t.Fatal(err)
}
Expand All @@ -105,7 +105,6 @@ func TestRoundTripper_RoundTripStatusOk(t *testing.T) {
}
}


func TestRoundTripper_RoundTripJsonStatusOk(t *testing.T) {

json := `{"hello":"world"}`
Expand All @@ -115,7 +114,7 @@ func TestRoundTripper_RoundTripJsonStatusOk(t *testing.T) {
atomic.AddInt32(&counter, 1)
t.Log("hit endpoint")

b , err := ioutil.ReadAll(req.Body)
b, err := ioutil.ReadAll(req.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
Expand All @@ -129,7 +128,6 @@ func TestRoundTripper_RoundTripJsonStatusOk(t *testing.T) {
return
}


if string(b) != json {
w.WriteHeader(http.StatusBadRequest)
return
Expand All @@ -139,7 +137,7 @@ func TestRoundTripper_RoundTripJsonStatusOk(t *testing.T) {
w.Write([]byte(json))
}))

retryRoundTripper := NewRoundTripper(http.DefaultTransport, .50 , .15 , 3, nil, new(Exp))
retryRoundTripper := NewRoundTripper(http.DefaultTransport, .50, .15, 3, nil, new(Exp))
httpClient := new(http.Client)
httpClient.Transport = retryRoundTripper

Expand All @@ -161,12 +159,12 @@ func TestRoundTripper_RoundTripJsonStatusOk(t *testing.T) {
t.Errorf("counter is bad, got=%v, want=%v", counter, 1)
}

b , err := ioutil.ReadAll(resp.Body)
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("response is bad, got=%v", err)
}

if string(b) != json {
t.Fatalf("response body is bad, got=%v", string(b))
}
}
}
9 changes: 9 additions & 0 deletions singleton/singleton.go
Expand Up @@ -13,6 +13,7 @@ type ConstructorFunc func() (interface{}, error)

type Singleton interface {
Get() (interface{}, error)
Reset()
}

// Call to create a new singleton that is instantiated with the given constructor function.
Expand All @@ -33,6 +34,7 @@ type singleton struct {
done uint32
}

// todo(marcel): Rename to GetOrCreate (major break)
func (s *singleton) Get() (interface{}, error) {
if atomic.LoadUint32(&s.done) == 1 {
return s.object, nil
Expand All @@ -51,3 +53,10 @@ func (s *singleton) Get() (interface{}, error) {

return s.object, nil
}

// Reset indicates that the next call of Get should actually a create instance
func (s *singleton) Reset() {
s.m.Lock()
defer s.m.Unlock()
atomic.StoreUint32(&s.done, 0)
}
51 changes: 51 additions & 0 deletions singleton/singleton_test.go
Expand Up @@ -33,6 +33,57 @@ func TestNewSingleton(t *testing.T) {
}
}

func TestSingletonReset(t *testing.T) {

var counter int
stubSingleton := NewSingleton(func() (interface{}, error) {
counter++
return counter, nil
})

object, err := stubSingleton.Get()
if err != nil {
t.Fatal(err)
}

expectedValue := 1

if object.(int) != expectedValue {
t.Fatalf(`unexpected error message (actual: "%d", expected: "%d")`, object.(int), expectedValue)
}

object, err = stubSingleton.Get()
if err != nil {
t.Fatal(err)
}

if object.(int) != expectedValue {
t.Fatalf(`unexpected error message (actual: "%d", expected: "%d")`, object.(int), expectedValue)
}

expectedValue = 2

stubSingleton.Reset()

object, err = stubSingleton.Get()
if err != nil {
t.Fatal(err)
}

if object.(int) != expectedValue {
t.Fatalf(`unexpected error message (actual: "%d", expected: "%d")`, object.(int), expectedValue)
}

object, err = stubSingleton.Get()
if err != nil {
t.Fatal(err)
}

if object.(int) != expectedValue {
t.Fatalf(`unexpected error message (actual: "%d", expected: "%d")`, object.(int), expectedValue)
}
}

func BenchmarkSingleton_Get(b *testing.B) {
stubSingleton := NewSingleton(func() (interface{}, error) {
return nil, nil
Expand Down
6 changes: 3 additions & 3 deletions xhttp/xhttp_test.go
Expand Up @@ -10,8 +10,8 @@ import (

type TestMiddleware struct {
roundtripper http.RoundTripper
Log func(v ...interface{})
ID int
Log func(v ...interface{})
ID int
}

func (m *TestMiddleware) RoundTrip(req *http.Request) (*http.Response, error) {
Expand Down Expand Up @@ -90,4 +90,4 @@ func TestPanicNilMiddlewares(t *testing.T) {
}()

xhttp.Use(new(http.Client))
}
}
2 changes: 1 addition & 1 deletion xpanic/handlepanic.go
Expand Up @@ -11,7 +11,7 @@ import (

// If crashOnError is set a coredump will be produced and kill program else it continues.
const (
CrashOnErrorActivated = true
CrashOnErrorActivated = true
CrashOnErrorDeactivated = false
)

Expand Down

0 comments on commit 1b783ea

Please sign in to comment.