-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[feat] Support periodic refresh of sampling strategies #2188
Conversation
4b44892
to
c8195f9
Compare
Codecov Report
@@ Coverage Diff @@
## master #2188 +/- ##
==========================================
+ Coverage 96.14% 96.16% +0.01%
==========================================
Files 219 219
Lines 10586 10628 +42
==========================================
+ Hits 10178 10220 +42
+ Misses 353 351 -2
- Partials 55 57 +2
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the PR. Note that it does not address #2186 as that one is about an external API (e.g. JSON endpoint). But periodic reloading is a good feature.
if err != nil { | ||
return ss, err | ||
} | ||
if s, ok := ss.(strategystore.StrategyUpdater); f.options.ReloadInterval > 0 && ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
strategystore.StrategyUpdater
interface is unnecessary at this time. This factory is already coupled with static storage, so it already knows that it supports Update method.
Also, it is better to implement the timer loop inside that storage, not in the factory.
for { | ||
select { | ||
case <-ticker.C: | ||
if currBytes, err := ioutil.ReadFile(filepath.Clean(filePath)); err == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if currBytes, err := ioutil.ReadFile(filepath.Clean(filePath)); err == nil { | |
currBytes, err := ioutil.ReadFile(filepath.Clean(filePath)) | |
if err != nil { | |
log | |
continue | |
} |
|
||
func TestAutoReload(t *testing.T) { | ||
// copy from fixtures/strategies.json | ||
srcFile, dstFile := "fixtures/strategies.json", "fixtures/strategies_for_reload.json" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
either use ioutil.TempFile()
or add strategies_for_reload.json
to fixtures/.gitignore
|
||
// Test reading strategies from a file | ||
//ctx, canf := context.WithCancel(context.Background()) | ||
//defer canf() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove?
} | ||
|
||
// AddFlags adds flags for Options | ||
func AddFlags(flagSet *flag.FlagSet) { | ||
flagSet.String(samplingStrategiesFile, "", "The path for the sampling strategies file in JSON format. See sampling documentation to see format of the file") | ||
flagSet.Duration(samplingStrategiesReloadInterval, 0, "Reload interval to check and reload sampling strategies file. Zero value means no checks (default 0s)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
flagSet.Duration(samplingStrategiesReloadInterval, 0, "Reload interval to check and reload sampling strategies file. Zero value means no checks (default 0s)") | |
flagSet.Duration(samplingStrategiesReloadInterval, 0, "Reload interval to check and reload sampling strategies file. Zero value means no reloading") |
the default value is printed automatically
@@ -23,3 +23,9 @@ type StrategyStore interface { | |||
// GetSamplingStrategy retrieves the sampling strategy for the specified service. | |||
GetSamplingStrategy(serviceName string) (*sampling.SamplingStrategyResponse, error) | |||
} | |||
|
|||
// StrategyUpdater updates sampling strategies. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not needed, see comments in the factory
|
||
f := NewFactory() | ||
v, command := config.Viperize(f.AddFlags) | ||
_ = command.ParseFlags([]string{"--sampling.strategies-file=" + dstFile, "--sampling.strategies-reload-interval=50ms"}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_ = command.ParseFlags([]string{"--sampling.strategies-file=" + dstFile, "--sampling.strategies-reload-interval=50ms"}) | |
_ = command.ParseFlags([]string{ | |
"--sampling.strategies-file=" + dstFile, | |
"--sampling.strategies-reload-interval=50ms", | |
}) |
require.NoError(t, err) | ||
|
||
// wait for reload | ||
time.Sleep(time.Millisecond * 50 * 2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sleeps are not reliable in CI. Please use a loop
for i:=0; i < 100; i++ { // wait up to 1sec
if condition {
break
}
time.Sleep(10ms)
}
assert
//ctx, canf := context.WithCancel(context.Background()) | ||
//defer canf() | ||
|
||
store, err := f.CreateStrategyStore() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This leaks a goroutine. There needs to be a way to shut down cleanly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we move reloading loop into storage, the test should move there as well (factory should only test parsing the options)
@@ -79,7 +78,7 @@ func TestPerOperationSamplingStrategies(t *testing.T) { | |||
os := s.OperationSampling | |||
assert.EqualValues(t, os.DefaultSamplingProbability, 0.8) | |||
require.Len(t, os.PerOperationStrategies, 4) | |||
fmt.Println(os) | |||
//fmt.Println(os) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove altogether
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for suggestions, I will remove it in the later revision.
a53bb83
to
8353fa6
Compare
Signed-off-by: defool <defool@foxmail.com>
} | ||
|
||
// StopUpdateStrategy stops updating the strategy | ||
func (h *strategyStore) StopUpdateStrategy() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
usually we call this Close()
return fmt.Errorf("failed to unmarshal strategies: %w", err) | ||
} | ||
h.parseStrategies(&strategies) | ||
h.logger.Info("Updated strategy:" + string(bytes)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
h.logger.Info("Updated strategy:" + string(bytes)) | |
h.logger.Info("Updated sampling strategies:" + string(bytes)) |
require.NoError(t, err) | ||
|
||
// wait for reload | ||
time.Sleep(interval * 2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as I mentioned, we cannot rely on a single sleep, it makes the tests unstable due to unpredictable pauses in the CI. Use a longer loop with short checks, in most cases it will exit quickly.
os.Remove(dstFile) | ||
|
||
// wait for delete and update failed | ||
time.Sleep(interval * 2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is this testing (and how)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The purpose is to increase the test code coverage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is there any other comment on this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of checking the error conditions via timer updates, I recommend refactoring the logic under case <-ticker.C
into a helper function reloadStrategiesFile
and unit-testing that function with the desired edge cases.
Signed-off-by: defool <defool@foxmail.com>
case <-ticker.C: | ||
currBytes, err := ioutil.ReadFile(filepath.Clean(filePath)) | ||
if err != nil { | ||
h.logger.Error("ReadFile failed", zap.Error(err)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
h.logger.Error("ReadFile failed", zap.Error(err)) | |
h.logger.Error("failed to load sampling strategies", zap.String("file", filePath), zap.Error(err)) | |
continue |
continue | ||
} | ||
if err = h.updateSamplingStrategy(currBytes); err != nil { | ||
h.logger.Error("UpdateSamplingStrategy failed", zap.Error(err)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
h.logger.Error("UpdateSamplingStrategy failed", zap.Error(err)) | |
h.logger.Error("failed to update sampling strategies from file", zap.Error(err)) |
func (h *strategyStore) updateSamplingStrategy(bytes []byte) error { | ||
var strategies strategies | ||
if err := json.Unmarshal(bytes, &strategies); err != nil { | ||
return fmt.Errorf("failed to unmarshal strategies: %w", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return fmt.Errorf("failed to unmarshal strategies: %w", err) | |
return fmt.Errorf("failed to unmarshal sampling strategies: %w", err) |
os.Remove(dstFile) | ||
|
||
// wait for delete and update failed | ||
time.Sleep(interval * 2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of checking the error conditions via timer updates, I recommend refactoring the logic under case <-ticker.C
into a helper function reloadStrategiesFile
and unit-testing that function with the desired edge cases.
Signed-off-by: Yuri Shkuro <ys@uber.com>
PR for #2186
Signed-off-by: defool defool@foxmail.com
Which problem is this PR solving?
Short description of the changes