Skip to content

Commit

Permalink
Add a hot reloading RIE side implementation (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
dfangl authored Dec 13, 2022
1 parent f19914e commit afe23d0
Show file tree
Hide file tree
Showing 8 changed files with 627 additions and 3 deletions.
65 changes: 65 additions & 0 deletions cmd/localstack/awsutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ package main

import (
"archive/zip"
"context"
"fmt"
"github.com/jessevdk/go-flags"
log "github.com/sirupsen/logrus"
"go.amzn.com/lambda/interop"
"go.amzn.com/lambda/rapidcore"
"io"
"io/fs"
"math"
"net/http"
"os"
Expand Down Expand Up @@ -196,6 +198,69 @@ func DownloadCodeArchive(url string) {

}

func resetListener(changeChannel <-chan bool, server *CustomInteropServer) {
for {
_, more := <-changeChannel
if !more {
return
}
log.Println("Resetting environment...")
_, err := server.Reset("HotReload", 2000)
if err != nil {
log.Warnln("Error resetting server: ", err)
}
}

}

func RunHotReloadingListener(server *CustomInteropServer, targetPaths []string, opts *LsOpts, ctx context.Context) {
if !opts.HotReloading {
log.Debugln("Hot reloading disabled.")
return
}
defaultDebouncingDuration := 500 * time.Millisecond
log.Infoln("Hot reloading enabled, starting filewatcher.", targetPaths)
changeListener, err := NewChangeListener(defaultDebouncingDuration)
if err != nil {
log.Errorln("Hot reloading disabled due to change listener error.", err)
return
}
defer changeListener.Close()
go changeListener.Start()
changeListener.AddTargetPaths(targetPaths)
go resetListener(changeListener.debouncedChannel, server)

<-ctx.Done()
log.Infoln("Closing down filewatcher.")

}

func getSubFolders(dirPath string) []string {
var subfolders []string
err := filepath.WalkDir(dirPath, func(path string, d fs.DirEntry, err error) error {
if err == nil && d.IsDir() {
subfolders = append(subfolders, path)
}
return err
})
if err != nil {
log.Errorln("Error listing directory contents: ", err)
return subfolders
}
return subfolders
}

func getSubFoldersInList(prefix string, pathList []string) (oldFolders []string, newFolders []string) {
for _, pathItem := range pathList {
if strings.HasPrefix(pathItem, prefix) {
oldFolders = append(oldFolders, pathItem)
} else {
newFolders = append(newFolders, pathItem)
}
}
return
}

func InitHandler(sandbox Sandbox, functionVersion string, timeout int64) (time.Time, time.Time) {
additionalFunctionEnvironmentVariables := map[string]string{}

Expand Down
69 changes: 69 additions & 0 deletions cmd/localstack/filenotify/filenotify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// This package is adapted from https://github.com/gohugoio/hugo/tree/master/watcher/filenotify, Apache-2.0 License.

// Package filenotify provides a mechanism for watching file(s) for changes.
// Generally leans on fsnotify, but provides a poll-based notifier which fsnotify does not support.
// These are wrapped up in a common interface so that either can be used interchangeably in your code.
//
// This package is adapted from https://github.com/moby/moby/tree/master/pkg/filenotify, Apache-2.0 License.
// Hopefully this can be replaced with an external package sometime in the future, see https://github.com/fsnotify/fsnotify/issues/9
package filenotify

import (
log "github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
"strings"
"time"

"github.com/fsnotify/fsnotify"
)

// FileWatcher is an interface for implementing file notification watchers
type FileWatcher interface {
Events() <-chan fsnotify.Event
Errors() <-chan error
Add(name string) error
Remove(name string) error
Close() error
}

func shouldUseEventWatcher() bool {
// Whether to use an event watcher or polling mechanism
var utsname unix.Utsname
err := unix.Uname(&utsname)
release := strings.TrimRight(string(utsname.Release[:]), "\x00")
log.Println("Release detected: ", release)
// cheap check if we are in Docker desktop or not.
// We could also inspect the mounts, but that would be more complicated and needs more parsing
return err == nil && !(strings.Contains(release, "linuxkit") || strings.Contains(release, "WSL2"))
}

// New tries to use a fs-event watcher, and falls back to the poller if there is an error
func New(interval time.Duration) (FileWatcher, error) {
if shouldUseEventWatcher() {
if watcher, err := NewEventWatcher(); err == nil {
log.Debugln("Using event based filewatcher")
return watcher, nil
}
}
log.Debugln("Using polling based filewatcher")
return NewPollingWatcher(interval), nil
}

// NewPollingWatcher returns a poll-based file watcher
func NewPollingWatcher(interval time.Duration) FileWatcher {
return &filePoller{
interval: interval,
done: make(chan struct{}),
events: make(chan fsnotify.Event),
errors: make(chan error),
}
}

// NewEventWatcher returns a fs-event based file watcher
func NewEventWatcher() (FileWatcher, error) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
return &fsNotifyWatcher{watcher}, nil
}
22 changes: 22 additions & 0 deletions cmd/localstack/filenotify/fsnotify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// This package is adapted from https://github.com/gohugoio/hugo/tree/master/watcher/filenotify, Apache-2.0 License.

// Package filenotify is adapted from https://github.com/moby/moby/tree/master/pkg/filenotify, Apache-2.0 License.
// Hopefully this can be replaced with an external package sometime in the future, see https://github.com/fsnotify/fsnotify/issues/9
package filenotify

import "github.com/fsnotify/fsnotify"

// fsNotifyWatcher wraps the fsnotify package to satisfy the FileNotifier interface
type fsNotifyWatcher struct {
*fsnotify.Watcher
}

// Events returns the fsnotify event channel receiver
func (w *fsNotifyWatcher) Events() <-chan fsnotify.Event {
return w.Watcher.Events
}

// Errors returns the fsnotify error channel receiver
func (w *fsNotifyWatcher) Errors() <-chan error {
return w.Watcher.Errors
}
Loading

0 comments on commit afe23d0

Please sign in to comment.