fix(config): use actual file modification time in UpdateLastFileModTime#1668
fix(config): use actual file modification time in UpdateLastFileModTime#1668RaminGe wants to merge 6 commits into
Conversation
Fixes TwiN#1657 Previously, UpdateLastFileModTime() set the last file modification time to time.Now(). In containerized environments with volume mounts (e.g. Kubernetes, Google Cloud Run), the mounted file's timestamp can be in the future relative to the container's internal clock. This caused the configuration watcher to continuously evaluate that the file had been modified (because config.lastFileModTime < fileInfo.ModTime()), triggering infinite restart loops and effectively disabling the alerting engine. This commit updates UpdateLastFileModTime() to read the actual ModTime of the config file using os.Stat (and if it is a directory, using the newest ModTime inside that directory). It falls back to time.Now() only if the true modification time cannot be determined.
|
@RaminGe 🐎🔥 |
TwiN
left a comment
There was a problem hiding this comment.
Makes sense. Thank you for the contribution!
|
@RaminGe is there any chance you could add a test for your change? |
9694312 to
e02d076
Compare
|
Hey @TwiN anything else needed on this? Would really appreciate a merge as we use Gatus for our production system monitoring and our custom alerting workaround sucks |
There was a problem hiding this comment.
Pull request overview
This PR fixes an infinite configuration hot-reload loop in containerized deployments by making Config.UpdateLastFileModTime() record the actual filesystem modification time (including directory-based configs), rather than using time.Now(). This aligns the watcher’s baseline with the mounted file’s timestamp (even if it is “in the future” relative to the container clock), preventing repeated false-positive “modified” detections that disrupt monitoring/alerting.
Changes:
- Update
UpdateLastFileModTime()to useos.Stat(...).ModTime()(or the newest YAML file ModTime within a config directory). - Add unit tests covering single-file, directory, and missing-file fallback behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
config/config.go |
Computes lastFileModTime from actual file/dir ModTime instead of time.Now(), preventing false reload loops. |
config/config_test.go |
Adds test coverage for the new ModTime behavior across file/dir/missing-path cases. |
| dir := t.TempDir() | ||
| configFilePath := filepath.Join(dir, "config.yaml") | ||
| _ = os.WriteFile(configFilePath, []byte(`endpoints: | ||
| - name: test | ||
| url: https://example.com | ||
| `), 0o644) | ||
| time.Sleep(10 * time.Millisecond) |
| config.UpdateLastFileModTime() | ||
| stat, _ := os.Stat(configFilePath) | ||
| if config.lastFileModTime != stat.ModTime() { | ||
| t.Errorf("expected lastFileModTime to be %v, got %v", stat.ModTime(), config.lastFileModTime) | ||
| } |
| configDir.UpdateLastFileModTime() | ||
| stat, _ := os.Stat(configFilePath) | ||
| if configDir.lastFileModTime != stat.ModTime() { | ||
| t.Errorf("expected lastFileModTime to be %v, got %v", stat.ModTime(), configDir.lastFileModTime) | ||
| } |
Summary
Fixes #1657
Previously,
UpdateLastFileModTime()set the last file modification time totime.Now(). In containerized environments with volume mounts (e.g. Kubernetes, Google Cloud Run), the mounted file's timestamp can be in the future relative to the container's internal clock. This caused the configuration watcher to continuously evaluate that the file had been modified (becauseconfig.lastFileModTime<fileInfo.ModTime()), triggering infinite restart loops and effectively disabling the alerting engine.This PR updates
UpdateLastFileModTime()to read the actual ModTime of the config file usingos.Stat(and if it is a directory, using the newest ModTime inside that directory). It falls back totime.Now()only if the true modification time cannot be determined.Checklist
README.md, if applicable. -> Not applicable