Skip to content

Commit

Permalink
feat: wasm module hot reload (#168)
Browse files Browse the repository at this point in the history
* feat: wasm module hot reload

Co-authored-by: seeflood <349895584@qq.com>
  • Loading branch information
zu1k and seeflood authored Aug 11, 2021
1 parent 77e0a4b commit 9a0cdbf
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 1 deletion.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.14
require (
github.com/dapr/components-contrib v1.2.0
github.com/dapr/kit v0.0.1
github.com/fsnotify/fsnotify v1.4.9
github.com/gammazero/workerpool v1.1.2
github.com/golang/mock v1.6.0
github.com/golang/protobuf v1.5.2
Expand All @@ -16,6 +17,7 @@ require (
github.com/tklauser/go-sysconf v0.3.5 // indirect
github.com/urfave/cli v1.22.1
github.com/valyala/fasthttp v1.26.0
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
google.golang.org/grpc v1.39.0
google.golang.org/grpc/examples v0.0.0-20210526223527-2de42fcbbce3 // indirect
google.golang.org/protobuf v1.27.1
Expand Down
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1455,8 +1455,9 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
1 change: 1 addition & 0 deletions pkg/wasm/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func createProxyWasmFilterFactory(conf map[string]interface{}) (api.StreamFilter
log.DefaultLogger.Errorf("[proxywasm][factory] createProxyWasmFilterFactory fail to add plugin, err: %v", err)
return nil, err
}
addWatchFile(config, pluginName)
} else {
pluginName = config.FromWasmPlugin
}
Expand Down
147 changes: 147 additions & 0 deletions pkg/wasm/watcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package wasm

import (
"os"
"path/filepath"
"strings"

v2 "mosn.io/mosn/pkg/config/v2"
"mosn.io/mosn/pkg/log"
"mosn.io/mosn/pkg/wasm"

"github.com/fsnotify/fsnotify"
)

var (
watcher *fsnotify.Watcher
configs = make(map[string]*filterConfig)
pluginNames = make(map[string]string)
)

func init() {
var err error
watcher, err = fsnotify.NewWatcher()
if err != nil {
log.DefaultLogger.Errorf("[proxywasm] [watcher] init fail to create watcher: %v", err)
return
}
go runWatcher()
}

func runWatcher() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
log.DefaultLogger.Errorf("[proxywasm] [watcher] runWatcher exit")
return
}
log.DefaultLogger.Debugf("[proxywasm] [watcher] runWatcher got event, %s", event)

if pathIsWasmFile(event.Name) {
if event.Op&fsnotify.Chmod == fsnotify.Chmod ||
event.Op&fsnotify.Rename == fsnotify.Rename {
continue
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
// rewatch the file if it exists
// remove this file then nename other file to this name will cause this case
if fileExist(event.Name) {
_ = watcher.Add(event.Name)
}
continue
} else if event.Op&fsnotify.Create == fsnotify.Create {
if fileExist(event.Name) {
_ = watcher.Add(event.Name)
}
}
reloadWasm(event.Name)
}
case err, ok := <-watcher.Errors:
if !ok {
log.DefaultLogger.Errorf("[proxywasm] [watcher] runWatcher exit")
return
}
log.DefaultLogger.Errorf("[proxywasm] [watcher] runWatcher got errors, err: %v", err)
}
}
}

func addWatchFile(cfg *filterConfig, pluginName string) {
path := cfg.VmConfig.Path
if err := watcher.Add(path); err != nil {
log.DefaultLogger.Errorf("[proxywasm] [watcher] addWatchFile fail to watch wasm file, err: %v", err)
return
}

dir := filepath.Dir(path)
if err := watcher.Add(dir); err != nil {
log.DefaultLogger.Errorf("[proxywasm] [watcher] addWatchFile fail to watch wasm dir, err: %v", err)
return
}

configs[path] = cfg
pluginNames[path] = pluginName
log.DefaultLogger.Infof("[proxywasm] [watcher] addWatchFile start to watch wasm file and its dir: %s", path)
}

func reloadWasm(fullPath string) {
found := false

for path, config := range configs {
if strings.HasSuffix(fullPath, path) {
found = true
pluginName := pluginNames[path]

err := wasm.GetWasmManager().UninstallWasmPluginByName(pluginName)
if err != nil {
log.DefaultLogger.Errorf("[proxywasm] [watcher] reloadWasm fail to uninstall plugin, err: %v", err)
}

v2Config := v2.WasmPluginConfig{
PluginName: pluginName,
VmConfig: config.VmConfig,
InstanceNum: config.InstanceNum,
}
err = wasm.GetWasmManager().AddOrUpdateWasm(v2Config)
if err != nil {
log.DefaultLogger.Errorf("[proxywasm] [watcher] reloadWasm fail to add plugin, err: %v", err)
return
}

pw := wasm.GetWasmManager().GetWasmPluginWrapperByName(pluginName)
if pw == nil {
log.DefaultLogger.Errorf("[proxywasm] [watcher] reloadWasm plugin not found")
return
}

factory := &FilterConfigFactory{
pluginName: pluginName,
config: config,
}
pw.RegisterPluginHandler(factory)

log.DefaultLogger.Infof("[proxywasm] [watcher] reloadWasm reload wasm success: %s", path)
}
}

if !found {
log.DefaultLogger.Errorf("[proxywasm] [watcher] reloadWasm WasmPluginConfig not found: %s", fullPath)
}
}

func fileExist(file string) bool {
_, err := os.Stat(file)
if err != nil && !os.IsExist(err) {
return false
}
return true
}

func pathIsWasmFile(fullPath string) bool {
for path, _ := range configs {
if strings.HasSuffix(fullPath, path) {
return true
}
}
return false
}

0 comments on commit 9a0cdbf

Please sign in to comment.