-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
lsp: Load config from parent dirs (#650)
* lsp: Load config from parent dirs Fixes #626 Signed-off-by: Charlie Egan <[email protected]> * lsp: watcher error, use := Signed-off-by: Charlie Egan <[email protected]> * lsp: Wait in server tests Server tests refactored a little to allow messages to be sent in a different order. Signed-off-by: Charlie Egan <[email protected]> * lsp: PR review comments Signed-off-by: Charlie Egan <[email protected]> * lsp: further reduce the config watch timeout Signed-off-by: Charlie Egan <[email protected]> * lsp: server test use shared timeout Signed-off-by: Charlie Egan <[email protected]> --------- Signed-off-by: Charlie Egan <[email protected]>
- Loading branch information
1 parent
fe1dc3a
commit c31886e
Showing
8 changed files
with
597 additions
and
207 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package config | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
|
||
"github.com/fsnotify/fsnotify" | ||
) | ||
|
||
type Watcher struct { | ||
Reload chan string | ||
Drop chan struct{} | ||
|
||
path string | ||
pathUpdates chan string | ||
|
||
fsWatcher *fsnotify.Watcher | ||
|
||
errorWriter io.Writer | ||
} | ||
|
||
type WatcherOpts struct { | ||
ErrorWriter io.Writer | ||
Path string | ||
} | ||
|
||
func NewWatcher(opts *WatcherOpts) *Watcher { | ||
w := &Watcher{ | ||
Reload: make(chan string, 1), | ||
Drop: make(chan struct{}, 1), | ||
pathUpdates: make(chan string, 1), | ||
} | ||
|
||
if opts != nil { | ||
w.errorWriter = opts.ErrorWriter | ||
w.path = opts.Path | ||
} | ||
|
||
return w | ||
} | ||
|
||
func (w *Watcher) Start(ctx context.Context) error { | ||
err := w.Stop() | ||
if err != nil { | ||
return fmt.Errorf("failed to stop existing watcher: %w", err) | ||
} | ||
|
||
w.fsWatcher, err = fsnotify.NewWatcher() | ||
if err != nil { | ||
return fmt.Errorf("failed to create fsnotify watcher: %w", err) | ||
} | ||
|
||
go func() { | ||
w.loop(ctx) | ||
}() | ||
|
||
return nil | ||
} | ||
|
||
func (w *Watcher) loop(ctx context.Context) { | ||
for { | ||
select { | ||
case path := <-w.pathUpdates: | ||
if w.path != "" { | ||
err := w.fsWatcher.Remove(w.path) | ||
if err != nil { | ||
fmt.Fprintf(w.errorWriter, "failed to remove existing watch: %v\n", err) | ||
} | ||
} | ||
|
||
err := w.fsWatcher.Add(path) | ||
if err != nil { | ||
fmt.Fprintf(w.errorWriter, "failed to add watch: %v\n", err) | ||
} | ||
|
||
w.path = path | ||
|
||
// when the path itself is changed, then this is an event too | ||
w.Reload <- path | ||
case event, ok := <-w.fsWatcher.Events: | ||
if !ok { | ||
fmt.Fprintf(w.errorWriter, "config watcher event channel closed\n") | ||
|
||
return | ||
} | ||
|
||
if event.Has(fsnotify.Write) { | ||
w.Reload <- event.Name | ||
} | ||
|
||
if event.Has(fsnotify.Remove) || event.Has(fsnotify.Rename) { | ||
w.path = "" | ||
w.Drop <- struct{}{} | ||
} | ||
case err := <-w.fsWatcher.Errors: | ||
fmt.Fprintf(w.errorWriter, "config watcher error: %v\n", err) | ||
case <-ctx.Done(): | ||
err := w.Stop() | ||
if err != nil { | ||
fmt.Fprintf(w.errorWriter, "failed to stop watcher: %v\n", err) | ||
} | ||
|
||
return | ||
} | ||
} | ||
} | ||
|
||
func (w *Watcher) Watch(configFilePath string) { | ||
w.pathUpdates <- configFilePath | ||
} | ||
|
||
func (w *Watcher) Stop() error { | ||
if w.fsWatcher != nil { | ||
err := w.fsWatcher.Close() | ||
if err != nil { | ||
return fmt.Errorf("failed to close fsnotify watcher: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (w *Watcher) IsWatching() bool { | ||
if w.fsWatcher == nil { | ||
return false | ||
} | ||
|
||
return len(w.fsWatcher.WatchList()) > 0 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package config | ||
|
||
import ( | ||
"context" | ||
"os" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func TestWatcher(t *testing.T) { | ||
t.Parallel() | ||
|
||
tempDir := t.TempDir() | ||
|
||
configFilePath := tempDir + "/config.yaml" | ||
|
||
configFileContents := `--- | ||
foo: bar | ||
` | ||
|
||
err := os.WriteFile(configFilePath, []byte(configFileContents), 0o600) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
watcher := NewWatcher(&WatcherOpts{ErrorWriter: os.Stderr}) | ||
|
||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
|
||
go func() { | ||
err = watcher.Start(ctx) | ||
if err != nil { | ||
t.Errorf("failed to start watcher: %v", err) | ||
} | ||
}() | ||
|
||
watcher.Watch(configFilePath) | ||
|
||
select { | ||
case <-watcher.Reload: | ||
case <-time.After(100 * time.Millisecond): | ||
t.Fatal("timeout waiting for initial config event") | ||
} | ||
|
||
newConfigFileContents := `--- | ||
foo: baz | ||
` | ||
|
||
err = os.WriteFile(configFilePath, []byte(newConfigFileContents), 0o600) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
select { | ||
case <-watcher.Reload: | ||
case <-time.After(100 * time.Millisecond): | ||
t.Fatal("timeout waiting for config event") | ||
} | ||
|
||
err = os.Rename(configFilePath, configFilePath+".new") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
select { | ||
case <-watcher.Drop: | ||
case <-time.After(100 * time.Millisecond): | ||
t.Fatal("timeout waiting for config drop event") | ||
} | ||
} |
Oops, something went wrong.