diff --git a/Gopkg.lock b/Gopkg.lock index 71a6d5e0..bd8e77c6 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -56,12 +56,12 @@ version = "v1.1.1" [[projects]] - digest = "1:00ebcb711885e2c33dec037f6ce3f3f20c60b82fb773497c15df1edfb74dffad" + digest = "1:cbad2721229e9862ce52396f7b1abe9bd822dc4b99daae37c96b1b504d767d61" name = "github.com/francoispqt/gojay" packages = ["."] pruneopts = "" - revision = "90d953358b68936b4791b8db205b3a5918b1b7a5" - version = "1.2.8" + revision = "eb6e5994c712d1e934f6bdd99188ac48317ef3c5" + version = "v1.2.10" [[projects]] digest = "1:eb53021a8aa3f599d29c7102e65026242bdedce998a54837dc67f14b6a97c5fd" @@ -100,7 +100,7 @@ version = "v1.2.0" [[projects]] - digest = "1:d3d38150b6d77b2aad42a9e105170538b6563518993d43df78a1add6e31cce62" + digest = "1:529d738b7976c3848cae5cf3a8036440166835e389c1f617af701eeb12a0518d" name = "github.com/golang/protobuf" packages = [ "proto", @@ -110,8 +110,8 @@ "ptypes/timestamp", ] pruneopts = "" - revision = "c823c79ea1570fb5ff454033735a8e68575d1d0f" - version = "v1.3.0" + revision = "b5d812f8a3706043e23a9cd5babf2e5423744d30" + version = "v1.3.1" [[projects]] digest = "1:6a6322a15aa8e99bd156fbba0aae4e5d67b4bb05251d860b348a45dfdcba9cce" @@ -130,7 +130,7 @@ version = "v1.1.1" [[projects]] - digest = "1:918e6d5252c1726e32b0597f5c3875cafbbae62cf6f78a79594931fd906143e1" + digest = "1:407d8e835f05c358958f8f8e142ca086131c3d90da4c2aa66e4f93c76585a131" name = "github.com/hashicorp/consul" packages = [ "api", @@ -139,8 +139,8 @@ "testutil/retry", ] pruneopts = "" - revision = "567e41ff6b096a478333c804d5c18264050bc3f8" - version = "v1.4.3" + revision = "ea5210a30e154f4da9a4c8e729b45b8ce7b9b92c" + version = "v1.4.4" [[projects]] digest = "1:8e3bd93036b4a925fe2250d3e4f38f21cadb8ef623561cd80c3c50c114b13201" @@ -151,12 +151,12 @@ version = "v1.0.0" [[projects]] - digest = "1:05334858a0cfb538622a066e065287f63f42bee26a7fda93a789674225057201" + digest = "1:984b627a3c838daa9f4c949ec8e6f049a7021b1156eb4db0337c3a5afe07aada" name = "github.com/hashicorp/go-cleanhttp" packages = ["."] pruneopts = "" - revision = "e8ab9daed8d1ddd2d3c4efba338fe2eeae2e4f18" - version = "v0.5.0" + revision = "eda1e5db218aad1db63ca4642c8906b26bcf2744" + version = "v0.5.1" [[projects]] digest = "1:c5466dfad4c14bf8e52a34b0eb98f1301acc1e304e0f0ff2c51c356ca1b86747" @@ -175,12 +175,12 @@ version = "v1.0.0" [[projects]] - digest = "1:776139dc18d63ef223ffaca5d8e9a3057174890f84393d3c881e934100b66dbc" + digest = "1:ec05166e7122b65982656b8777c8f6ad346e9ef224f854f4123d62b11183e725" name = "github.com/hashicorp/go-retryablehttp" packages = ["."] pruneopts = "" - revision = "73489d0a1476f0c9e6fb03f9c39241523a496dfd" - version = "v0.5.2" + revision = "357460732517ec3b57c05c51443296bdd6df1874" + version = "v0.5.3" [[projects]] digest = "1:c25fc9af3d03f56e1d8827e6d823dcc3318be2902d8182601cf7538834bb346f" @@ -242,7 +242,7 @@ version = "v0.8.2" [[projects]] - digest = "1:f45b7e07f44ceda7d619a2b1307e19468321f6d06897d63e3fdafd20261c0f77" + digest = "1:22e8823a6666d334428545295d019a0e69eef28dd810005e866a411d64b1202d" name = "github.com/hashicorp/vault" packages = [ "api", @@ -254,8 +254,8 @@ "helper/strutil", ] pruneopts = "" - revision = "85909e3373aa743c34a6a0ab59131f61fd9e8e43" - version = "v1.0.3" + revision = "36aa8c8dd1936e10ebd7a4c1d412ae0e6f7900bd" + version = "v1.1.0" [[projects]] digest = "1:31bfd110d31505e9ffbc9478e31773bf05bf02adcaeb9b139af42684f9294c13" @@ -274,7 +274,7 @@ revision = "7e38e58719c33e0d44d585c4ab477a30f8cb82dd" [[projects]] - digest = "1:f57ebba94f901202a52132284a01a272d833b158cb5b579bae6b75915bcddc8d" + digest = "1:1388833829c8ba1e920263e1ef95cad3b41b615a9572b0fcc7aa23a375a50b8a" name = "github.com/lalamove/nui" packages = [ "ncontext", @@ -284,8 +284,8 @@ "nstrings", ] pruneopts = "" - revision = "d2745b1cf314264f7fc8068dc9a82c8078f8fa4d" - version = "v0.1.0" + revision = "6613b55f1fc2c114959978642c421dea20798682" + version = "v0.2.0" [[projects]] digest = "1:961dc3b1d11f969370533390fdf203813162980c858e1dabe827b60940c909a5" @@ -367,15 +367,15 @@ version = "v1.2.0" [[projects]] - digest = "1:21b276e792044150a96175edd51d653649b9fd175c51a4613f2741b411c6d674" + digest = "1:eea52fd418b317ff9afc060cd1697b841f3b22e30df465271c0b6049c479c3b4" name = "github.com/pierrec/lz4" packages = [ ".", "internal/xxh32", ] pruneopts = "" - revision = "062282ea0dcff40c9fb8525789eef9644b1fbd6e" - version = "v2.1.0" + revision = "315a67e90e415bcdaff33057da191569bf4d8479" + version = "v2.1.1" [[projects]] digest = "1:1d7e1867c49a6dd9856598ef7c3123604ea3daabf5b83f303ff457bcbc410b1d" @@ -426,17 +426,11 @@ [[projects]] branch = "master" - digest = "1:a29813277de981ce33148b3f1906bba492af2ce97237c050f62a6b48cfdae721" + digest = "1:6326cc7b48adc2a0ae4f9b8decc12c6545ebbd4a07e3a6a25c86ebd22a1af4d0" name = "github.com/prometheus/procfs" - packages = [ - ".", - "internal/util", - "iostats", - "nfs", - "xfs", - ] + packages = ["."] pruneopts = "" - revision = "d0f344d83b0c80a1bc03b547a2374a9ec6711144" + revision = "af7bedc223fba51343f25a6fa7aba84355d0357a" [[projects]] digest = "1:2389dc36373649d729e089057acf50aca054098f4e252596754d9ea76277fc07" @@ -455,15 +449,15 @@ version = "v1.0.0" [[projects]] - digest = "1:d9f371ceb44045ca4405b65fed916e4b45396340b9e8674851c3d652f6351cde" + digest = "1:956f655c87b7255c6b1ae6c203ebb0af98cf2a13ef2507e34c9bf1c0332ac0f5" name = "github.com/spf13/afero" packages = [ ".", "mem", ] pruneopts = "" - revision = "f4711e4db9e9a1d3887343acb72b2bbfc2f686f5" - version = "v1.2.1" + revision = "588a75ec4f32903aa5e39a2619ba6a4631e28424" + version = "v1.2.2" [[projects]] digest = "1:ae3493c780092be9d576a1f746ab967293ec165e8473425631f06658b6212afc" @@ -518,7 +512,7 @@ [[projects]] branch = "master" - digest = "1:e9152dabd6b276edd0f1d51598e5745dcda9411315d216863bb46b13e523300c" + digest = "1:adcb9e84ce154ef1d45851b57c40f8a211db3e36373a65b7c4f10c79b7428718" name = "golang.org/x/net" packages = [ "context", @@ -530,15 +524,15 @@ "trace", ] pruneopts = "" - revision = "5c2c250b6a70e39b74bb544433a348a892b10530" + revision = "74de082e2cca95839e88aa0aeee5aadf6ce7710f" [[projects]] branch = "master" - digest = "1:9c1e747af61ab955fba74a79e87d5843a5d1d504e3ef86480f76e6a4e3a49136" + digest = "1:971c09284151966485209915a5d0500fe8a07376f31906db0ad8f8417237499b" name = "golang.org/x/sys" packages = ["unix"] pruneopts = "" - revision = "fead79001313d15903fb4605b4a1b781532cd93e" + revision = "e4093980e83e70784fc393a93d5620e2654097f0" [[projects]] digest = "1:5acd3512b047305d49e8763eef7ba423901e85d5dd2fd1e71778a0ea8de10bd4" @@ -573,14 +567,14 @@ [[projects]] branch = "master" - digest = "1:6a8803e2481c92e20f9fc654b8ddfbd5a2a802c0454d0bc5b7a018b218282205" + digest = "1:8cdef6da940ed0c6146559d452b3665683f56d276ed0b0455c461411e9476b81" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] pruneopts = "" - revision = "5fe7a883aa19554f42890211544aa549836af7b7" + revision = "f467c93bbac2133ff463e1f93d18d8f9f3f04451" [[projects]] - digest = "1:b6c17b0c657f047118ae59b358950b28515914512cf3de02388cc42e33a92520" + digest = "1:9e9c6b3804858b1a058ef81027270d307b74f4ddd727d2edff0ca002dd304adb" name = "google.golang.org/grpc" packages = [ ".", @@ -617,8 +611,8 @@ "tap", ] pruneopts = "" - revision = "2fdaae294f38ed9a121193c51ec99fecd3b13eb7" - version = "v1.19.0" + revision = "3507fb8e1a5ad030303c106fef3a47c9fdad16ad" + version = "v1.19.1" [[projects]] digest = "1:cedccf16b71e86db87a24f8d4c70b0a855872eb967cb906a66b95de56aefbd0d" diff --git a/Gopkg.toml b/Gopkg.toml index 59333b12..9a11d1f6 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -35,4 +35,4 @@ [[constraint]] name = "github.com/lalamove/nui" - version = "~0.1.0" + version = "~0.2.0" diff --git a/config.go b/config.go index 307c0799..30e5a3a1 100644 --- a/config.go +++ b/config.go @@ -66,6 +66,8 @@ type Config struct { Logger nlogger.Provider // Metrics sets whether a konfig.Store should record metrics for config loaders Metrics bool + // MaxWatcherPanics is the maximum number of times to restart a watcher when it panics, default is 0. + MaxWatcherPanics int } // Store is the interface diff --git a/config_test.go b/config_test.go index be7c8e9e..f7f55ac4 100644 --- a/config_test.go +++ b/config_test.go @@ -141,6 +141,7 @@ func TestConfigWatcherLoader(t *testing.T) { gomock.InOrder( l.EXPECT().Load(Values{}).Return(errors.New("")), + l.EXPECT().Name().Return("foo"), l.EXPECT().Load(Values{}).Return(nil), ) @@ -163,6 +164,7 @@ func TestConfigWatcherLoader(t *testing.T) { gomock.InOrder( l.EXPECT().Load(Values{}).Return(errors.New("")), + l.EXPECT().Name().Return("l"), l.EXPECT().Load(Values{}).Return(nil), ) @@ -186,8 +188,11 @@ func TestConfigWatcherLoader(t *testing.T) { gomock.InOrder( l.EXPECT().Load(Values{}).Return(errors.New("")), + l.EXPECT().Name().Return("l"), l.EXPECT().Load(Values{}).Return(errors.New("")), + l.EXPECT().Name().Return("l"), l.EXPECT().Load(Values{}).Return(errors.New("")), + l.EXPECT().Name().Return("l"), l.EXPECT().StopOnFailure().Return(false), ) @@ -214,8 +219,11 @@ func TestConfigWatcherLoader(t *testing.T) { gomock.InOrder( l.EXPECT().Load(Values{}).Return(nil), l.EXPECT().Load(Values{}).Return(errors.New("")), + l.EXPECT().Name().Return("l"), l.EXPECT().Load(Values{}).Return(errors.New("")), + l.EXPECT().Name().Return("l"), l.EXPECT().Load(Values{}).Return(errors.New("")), + l.EXPECT().Name().Return("l"), l.EXPECT().StopOnFailure().Return(true), ) wl.EXPECT().Start().Times(1).Return(nil) @@ -263,8 +271,11 @@ func TestConfigWatcherLoader(t *testing.T) { gomock.InOrder( l.EXPECT().Load(Values{}).Return(nil), l.EXPECT().Load(Values{}).Return(errors.New("")), + l.EXPECT().Name().Return("l"), l.EXPECT().Load(Values{}).Return(errors.New("")), + l.EXPECT().Name().Return("l"), l.EXPECT().Load(Values{}).Return(errors.New("")), + l.EXPECT().Name().Return("l"), l.EXPECT().StopOnFailure().Return(true), ) wl2.EXPECT().Start().Times(1).Return(nil) diff --git a/go.mod b/go.mod index c209c816..c65ca539 100644 --- a/go.mod +++ b/go.mod @@ -56,6 +56,7 @@ require ( github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect github.com/grpc-ecosystem/grpc-gateway v1.8.1 // indirect github.com/hashicorp/consul v1.4.2 + github.com/hashicorp/go-cleanhttp v0.5.1 // indirect github.com/hashicorp/go-gcp-common v0.0.0-20180425173946-763e39302965 // indirect github.com/hashicorp/go-hclog v0.8.0 // indirect github.com/hashicorp/go-memdb v0.0.0-20181108192425-032f93b25bec // indirect @@ -82,7 +83,7 @@ require ( github.com/jefferai/jsonx v1.0.0 // indirect github.com/jinzhu/copier v0.0.0-20180308034124-7e38e58719c3 github.com/keybase/go-crypto v0.0.0-20181127160227-255a5089e85a // indirect - github.com/lalamove/nui v0.1.0 + github.com/lalamove/nui v0.2.0 github.com/mattbaird/elastigo v0.0.0-20170123220020-2fe47fd29e4b // indirect github.com/michaelklishin/rabbit-hole v1.5.0 // indirect github.com/micro/go-config v1.0.0 @@ -98,6 +99,7 @@ require ( github.com/ory/dockertest v3.3.4+incompatible // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pierrec/lz4 v0.0.0-20181005164709-635575b42742 // indirect + github.com/pkg/errors v0.8.1 github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect github.com/pquerna/otp v1.1.0 // indirect github.com/prometheus/client_golang v0.9.2 diff --git a/go.sum b/go.sum index dd92fcf5..de67dedc 100644 --- a/go.sum +++ b/go.sum @@ -209,6 +209,8 @@ github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/U github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-gcp-common v0.0.0-20180425173946-763e39302965 h1:CRWAJ7atEOq9OfAa5rBKvL4wFNbUrPil8hN1knLuEG0= github.com/hashicorp/go-gcp-common v0.0.0-20180425173946-763e39302965/go.mod h1:LNbios2fdMAuLA1dsYUvUcoCYIfywcCEK8/ooaWjoOA= github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= @@ -313,6 +315,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lalamove/nui v0.1.0 h1:dG/KfB4IoFejTUuOZ+fR+f98p9V1HVBXQ6LYWtdT7NM= github.com/lalamove/nui v0.1.0/go.mod h1:IMgw/td9JHjoU1dpHjGj6o1i7bSeG/UO1/DODuPdz+o= +github.com/lalamove/nui v0.2.0 h1:IG6BxqLcIvMB2kdw0kBfm8ZKxlAmedJWgChZXdvIYso= +github.com/lalamove/nui v0.2.0/go.mod h1:IMgw/td9JHjoU1dpHjGj6o1i7bSeG/UO1/DODuPdz+o= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= diff --git a/konfig.go b/konfig.go new file mode 100644 index 00000000..3bb1c15f --- /dev/null +++ b/konfig.go @@ -0,0 +1,45 @@ +// Package konfig provides a composable, observable and performant +// config handling for Go. +// Written for larger distributed systems where you may have plenty of +// configuration sources - it allows you to compose configurations from +// multiple sources with reload hooks making it simple to build apps that live +// in a highly dynamic environment. +// +// Konfig is built around 4 small interfaces: Loader, Watcher, Parser, Closer +// +// Get started: +// var configFiles = []klfile.File{ +// { +// Path: "./config.json", +// Parser: kpjson.Parser, +// }, +// } +// +// func init() { +// konfig.Init(konfig.DefaultConfig()) +// } +// +// func main() { +// // load from json file with a file wacher +// konfig.RegisterLoaderWatcher( +// klfile.New(&klfile.Config{ +// Files: configFiles, +// Watch: true, +// }), +// // optionally you can pass config hooks to run when a file is changed +// func(c konfig.Store) error { +// return nil +// }, +// ) +// +// // Load and start watching +// if err := konfig.LoadWatch(); err != nil { +// log.Fatal(err) +// } +// +// // retrieve value from config file +// konfig.Bool("debug") +// } +// +// +package konfig diff --git a/loader.go b/loader.go index 6b416627..46b858ab 100644 --- a/loader.go +++ b/loader.go @@ -128,8 +128,14 @@ func (c *S) loaderLoadRetry(wl *loaderWatcher, retry int) error { // we call the loader if err := wl.Load(v); err != nil { + c.cfg.Logger.Get().Error(fmt.Sprintf( + "Error %d in loader %s: %s", + retry, + wl.Name(), + err.Error(), + )) + if retry >= wl.MaxRetry() { - c.cfg.Logger.Get().Error(err.Error()) return err } @@ -166,17 +172,28 @@ func (c *S) loaderLoadRetry(wl *loaderWatcher, retry int) error { return nil } -func (c *S) watchLoader(wl *loaderWatcher) { - // if a panic occurs close everything +func (c *S) watchLoader(wl *loaderWatcher, panics int) { + // if a panic occurs we log it + // then, if the current loader requires a to stop on failure, we stop everything, + // else, we restart the watcher. defer func() { if r := recover(); r != nil { - c.cfg.Logger.Get().Error(fmt.Sprintf("%v", r)) - c.stop() - return + c.cfg.Logger.Get().Error( + fmt.Sprintf( + "Panic %d in loader %s: %v", + panics, + wl.Name(), + r, + ), + ) + if wl.StopOnFailure() || panics >= c.cfg.MaxWatcherPanics { + c.stop() + return + } + go c.watchLoader(wl, panics+1) } }() - // make sure we recover from panics for { select { case <-wl.Done(): diff --git a/loader_test.go b/loader_test.go index 304e3731..3b763407 100644 --- a/loader_test.go +++ b/loader_test.go @@ -81,12 +81,13 @@ func TestLoaderLoadRetry(t *testing.T) { }, }, { - name: "success, no loader hooks, 1 retrty", + name: "success, no loader hooks, 1 retry", build: func(ctrl *gomock.Controller) *loaderWatcher { var mockW = NewMockWatcher(ctrl) var mockL = NewMockLoader(ctrl) gomock.InOrder( mockL.EXPECT().Load(Values{}).Return(errors.New("")), + mockL.EXPECT().Name().Return("l"), mockL.EXPECT().MaxRetry().Return(1), mockL.EXPECT().RetryDelay().Return(1*time.Millisecond), mockL.EXPECT().Load(Values{}).Return(nil), @@ -107,9 +108,11 @@ func TestLoaderLoadRetry(t *testing.T) { var mockL = NewMockLoader(ctrl) gomock.InOrder( mockL.EXPECT().Load(Values{}).Return(errors.New("")), + mockL.EXPECT().Name().Return("l"), mockL.EXPECT().MaxRetry().Return(1), mockL.EXPECT().RetryDelay().Return(1*time.Millisecond), mockL.EXPECT().Load(Values{}).Return(errors.New("")), + mockL.EXPECT().Name().Return("l"), mockL.EXPECT().MaxRetry().Return(1), ) var wl = &loaderWatcher{ @@ -127,6 +130,7 @@ func TestLoaderLoadRetry(t *testing.T) { var mockL = NewMockLoader(ctrl) gomock.InOrder( mockL.EXPECT().Load(Values{}).Return(errors.New("")), + mockL.EXPECT().Name().Return("l"), mockL.EXPECT().MaxRetry().Return(1), mockL.EXPECT().RetryDelay().Return(1*time.Millisecond), mockL.EXPECT().Load(Values{}).Return(nil), @@ -154,6 +158,7 @@ func TestLoaderLoadRetry(t *testing.T) { var mockL = NewMockLoader(ctrl) gomock.InOrder( mockL.EXPECT().Load(Values{}).Return(errors.New("")), + mockL.EXPECT().Name().Return("l"), mockL.EXPECT().MaxRetry().Return(1), mockL.EXPECT().RetryDelay().Return(1*time.Millisecond), mockL.EXPECT().Load(Values{}).Return(nil), @@ -212,6 +217,7 @@ func TestLoaderLoadRetryStrictKeys(t *testing.T) { mockL.EXPECT().Load(Values{}).Do(func(v Values) { v["test"] = "test" }).Return(errors.New("")), + mockL.EXPECT().Name().Return("l"), mockL.EXPECT().MaxRetry().Return(1), mockL.EXPECT().RetryDelay().Return(1*time.Millisecond), mockL.EXPECT().Load(Values{}).Do(func(v Values) { @@ -221,6 +227,7 @@ func TestLoaderLoadRetryStrictKeys(t *testing.T) { mockL.EXPECT().Load(Values{}).Do(func(v Values) { v["test"] = "test" }).Return(errors.New("")), + mockL.EXPECT().Name().Return("l"), mockL.EXPECT().MaxRetry().Return(1), mockL.EXPECT().RetryDelay().Return(1*time.Millisecond), mockL.EXPECT().Load(Values{}).Do(func(v Values) { @@ -363,9 +370,10 @@ func TestLoaderLoadRetryKeyHooks(t *testing.T) { func TestLoaderLoadWatch(t *testing.T) { var testCases = []struct { - name string - err bool - build func(ctrl *gomock.Controller) *loaderWatcher + name string + err bool + maxPanics int + build func(ctrl *gomock.Controller) *loaderWatcher }{ { name: "success, no errors", @@ -395,7 +403,7 @@ func TestLoaderLoadWatch(t *testing.T) { var mockL = NewMockLoader(ctrl) mockL.EXPECT().Name().MinTimes(1).Return("test") - mockL.EXPECT().Load(Values{}).Times(4).Return(errors.New("")) + mockL.EXPECT().Load(Values{}).Times(4).Return(errors.New("some err")) mockL.EXPECT().MaxRetry().Times(4).Return(3) mockL.EXPECT().RetryDelay().Times(3).Return(50 * time.Millisecond) mockL.EXPECT().StopOnFailure().Return(true) @@ -409,6 +417,78 @@ func TestLoaderLoadWatch(t *testing.T) { return wl }, }, + { + name: "panic load after watch, maxPanics 0", + build: func(ctrl *gomock.Controller) *loaderWatcher { + var mockW = NewMockWatcher(ctrl) + var mockL = NewMockLoader(ctrl) + + var c = make(chan struct{}, 1) + var d = make(chan struct{}) + + mockL.EXPECT().Name().MinTimes(1).Return("test") + mockL.EXPECT().Load(Values{}).Return(nil) + mockW.EXPECT().Start().Return(nil) + mockW.EXPECT().Watch().MinTimes(1).Return(c) + mockW.EXPECT().Done().MinTimes(1).Return(d) + + mockL.EXPECT().Load(Values{}).Do(func(Values) error { + panic(errors.New("some err")) + }).Return(nil) + mockL.EXPECT().StopOnFailure().Return(false) + mockW.EXPECT().Close().Return(nil) + + var wl = &loaderWatcher{ + Watcher: mockW, + Loader: mockL, + loaderHooks: nil, + } + // trigger a watch + c <- struct{}{} + return wl + }, + }, + { + + name: "panic load after watch, maxPanics 2", + maxPanics: 2, + build: func(ctrl *gomock.Controller) *loaderWatcher { + var mockW = NewMockWatcher(ctrl) + var mockL = NewMockLoader(ctrl) + + var c = make(chan struct{}, 3) + var d = make(chan struct{}) + + mockL.EXPECT().Name().MinTimes(1).Return("test") + mockL.EXPECT().Load(Values{}).Return(nil) + mockW.EXPECT().Start().Return(nil) + mockW.EXPECT().Watch().MinTimes(1).Return(c) + mockW.EXPECT().Done().MinTimes(1).Return(d) + + gomock.InOrder( + mockL.EXPECT().Load(Values{}).Do(func(Values) error { + panic(errors.New("some err")) + }).Return(nil), + mockL.EXPECT().StopOnFailure().Return(false), + mockL.EXPECT().Load(Values{}).Do(func(Values) error { + panic(errors.New("some err")) + }).Return(nil), + mockL.EXPECT().StopOnFailure().Return(false), + mockW.EXPECT().Close().Return(nil), + ) + + var wl = &loaderWatcher{ + Watcher: mockW, + Loader: mockL, + loaderHooks: nil, + } + // trigger a watch + c <- struct{}{} + c <- struct{}{} + c <- struct{}{} + return wl + }, + }, } for _, testCase := range testCases { @@ -418,10 +498,9 @@ func TestLoaderLoadWatch(t *testing.T) { var ctrl = gomock.NewController(t) defer ctrl.Finish() - reset() - var c = New(&Config{ - Metrics: true, + Metrics: true, + MaxWatcherPanics: testCase.maxPanics, }) c.RegisterLoaderWatcher( diff --git a/watcher.go b/watcher.go index bc2da1ca..70a7b4ce 100644 --- a/watcher.go +++ b/watcher.go @@ -69,7 +69,7 @@ func (c *S) Watch() error { if err := wl.Start(); err != nil { return err } - go c.watchLoader(wl) + go c.watchLoader(wl, 1) } return nil }