Skip to content

Commit afe23d0

Browse files
authored
Add a hot reloading RIE side implementation (#7)
1 parent f19914e commit afe23d0

File tree

8 files changed

+627
-3
lines changed

8 files changed

+627
-3
lines changed

Diff for: cmd/localstack/awsutil.go

+65
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ package main
77

88
import (
99
"archive/zip"
10+
"context"
1011
"fmt"
1112
"github.com/jessevdk/go-flags"
1213
log "github.com/sirupsen/logrus"
1314
"go.amzn.com/lambda/interop"
1415
"go.amzn.com/lambda/rapidcore"
1516
"io"
17+
"io/fs"
1618
"math"
1719
"net/http"
1820
"os"
@@ -196,6 +198,69 @@ func DownloadCodeArchive(url string) {
196198

197199
}
198200

201+
func resetListener(changeChannel <-chan bool, server *CustomInteropServer) {
202+
for {
203+
_, more := <-changeChannel
204+
if !more {
205+
return
206+
}
207+
log.Println("Resetting environment...")
208+
_, err := server.Reset("HotReload", 2000)
209+
if err != nil {
210+
log.Warnln("Error resetting server: ", err)
211+
}
212+
}
213+
214+
}
215+
216+
func RunHotReloadingListener(server *CustomInteropServer, targetPaths []string, opts *LsOpts, ctx context.Context) {
217+
if !opts.HotReloading {
218+
log.Debugln("Hot reloading disabled.")
219+
return
220+
}
221+
defaultDebouncingDuration := 500 * time.Millisecond
222+
log.Infoln("Hot reloading enabled, starting filewatcher.", targetPaths)
223+
changeListener, err := NewChangeListener(defaultDebouncingDuration)
224+
if err != nil {
225+
log.Errorln("Hot reloading disabled due to change listener error.", err)
226+
return
227+
}
228+
defer changeListener.Close()
229+
go changeListener.Start()
230+
changeListener.AddTargetPaths(targetPaths)
231+
go resetListener(changeListener.debouncedChannel, server)
232+
233+
<-ctx.Done()
234+
log.Infoln("Closing down filewatcher.")
235+
236+
}
237+
238+
func getSubFolders(dirPath string) []string {
239+
var subfolders []string
240+
err := filepath.WalkDir(dirPath, func(path string, d fs.DirEntry, err error) error {
241+
if err == nil && d.IsDir() {
242+
subfolders = append(subfolders, path)
243+
}
244+
return err
245+
})
246+
if err != nil {
247+
log.Errorln("Error listing directory contents: ", err)
248+
return subfolders
249+
}
250+
return subfolders
251+
}
252+
253+
func getSubFoldersInList(prefix string, pathList []string) (oldFolders []string, newFolders []string) {
254+
for _, pathItem := range pathList {
255+
if strings.HasPrefix(pathItem, prefix) {
256+
oldFolders = append(oldFolders, pathItem)
257+
} else {
258+
newFolders = append(newFolders, pathItem)
259+
}
260+
}
261+
return
262+
}
263+
199264
func InitHandler(sandbox Sandbox, functionVersion string, timeout int64) (time.Time, time.Time) {
200265
additionalFunctionEnvironmentVariables := map[string]string{}
201266

Diff for: cmd/localstack/filenotify/filenotify.go

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// This package is adapted from https://github.com/gohugoio/hugo/tree/master/watcher/filenotify, Apache-2.0 License.
2+
3+
// Package filenotify provides a mechanism for watching file(s) for changes.
4+
// Generally leans on fsnotify, but provides a poll-based notifier which fsnotify does not support.
5+
// These are wrapped up in a common interface so that either can be used interchangeably in your code.
6+
//
7+
// This package is adapted from https://github.com/moby/moby/tree/master/pkg/filenotify, Apache-2.0 License.
8+
// Hopefully this can be replaced with an external package sometime in the future, see https://github.com/fsnotify/fsnotify/issues/9
9+
package filenotify
10+
11+
import (
12+
log "github.com/sirupsen/logrus"
13+
"golang.org/x/sys/unix"
14+
"strings"
15+
"time"
16+
17+
"github.com/fsnotify/fsnotify"
18+
)
19+
20+
// FileWatcher is an interface for implementing file notification watchers
21+
type FileWatcher interface {
22+
Events() <-chan fsnotify.Event
23+
Errors() <-chan error
24+
Add(name string) error
25+
Remove(name string) error
26+
Close() error
27+
}
28+
29+
func shouldUseEventWatcher() bool {
30+
// Whether to use an event watcher or polling mechanism
31+
var utsname unix.Utsname
32+
err := unix.Uname(&utsname)
33+
release := strings.TrimRight(string(utsname.Release[:]), "\x00")
34+
log.Println("Release detected: ", release)
35+
// cheap check if we are in Docker desktop or not.
36+
// We could also inspect the mounts, but that would be more complicated and needs more parsing
37+
return err == nil && !(strings.Contains(release, "linuxkit") || strings.Contains(release, "WSL2"))
38+
}
39+
40+
// New tries to use a fs-event watcher, and falls back to the poller if there is an error
41+
func New(interval time.Duration) (FileWatcher, error) {
42+
if shouldUseEventWatcher() {
43+
if watcher, err := NewEventWatcher(); err == nil {
44+
log.Debugln("Using event based filewatcher")
45+
return watcher, nil
46+
}
47+
}
48+
log.Debugln("Using polling based filewatcher")
49+
return NewPollingWatcher(interval), nil
50+
}
51+
52+
// NewPollingWatcher returns a poll-based file watcher
53+
func NewPollingWatcher(interval time.Duration) FileWatcher {
54+
return &filePoller{
55+
interval: interval,
56+
done: make(chan struct{}),
57+
events: make(chan fsnotify.Event),
58+
errors: make(chan error),
59+
}
60+
}
61+
62+
// NewEventWatcher returns a fs-event based file watcher
63+
func NewEventWatcher() (FileWatcher, error) {
64+
watcher, err := fsnotify.NewWatcher()
65+
if err != nil {
66+
return nil, err
67+
}
68+
return &fsNotifyWatcher{watcher}, nil
69+
}

Diff for: cmd/localstack/filenotify/fsnotify.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// This package is adapted from https://github.com/gohugoio/hugo/tree/master/watcher/filenotify, Apache-2.0 License.
2+
3+
// Package filenotify is adapted from https://github.com/moby/moby/tree/master/pkg/filenotify, Apache-2.0 License.
4+
// Hopefully this can be replaced with an external package sometime in the future, see https://github.com/fsnotify/fsnotify/issues/9
5+
package filenotify
6+
7+
import "github.com/fsnotify/fsnotify"
8+
9+
// fsNotifyWatcher wraps the fsnotify package to satisfy the FileNotifier interface
10+
type fsNotifyWatcher struct {
11+
*fsnotify.Watcher
12+
}
13+
14+
// Events returns the fsnotify event channel receiver
15+
func (w *fsNotifyWatcher) Events() <-chan fsnotify.Event {
16+
return w.Watcher.Events
17+
}
18+
19+
// Errors returns the fsnotify error channel receiver
20+
func (w *fsNotifyWatcher) Errors() <-chan error {
21+
return w.Watcher.Errors
22+
}

0 commit comments

Comments
 (0)