From ba9ae6e3917fefa0c6e426803c36d00fb6498424 Mon Sep 17 00:00:00 2001 From: William Petit Date: Mon, 24 Apr 2023 12:16:30 +0200 Subject: [PATCH] fix(app): use event loop runtime for every operations --- cmd/cli/command/app/run.go | 2 +- pkg/app/promise_proxy.go | 11 +++ pkg/app/server.go | 124 ++++++++++++++++++++------------- pkg/app/server_module.go | 2 +- pkg/module/auth/module.go | 4 ++ pkg/module/cast/module.go | 23 +++--- pkg/module/cast/module_test.go | 2 +- pkg/module/lifecycle.go | 38 ++++++---- pkg/module/rpc.go | 12 +++- 9 files changed, 137 insertions(+), 81 deletions(-) diff --git a/cmd/cli/command/app/run.go b/cmd/cli/command/app/run.go index c15f66d..5eb337c 100644 --- a/cmd/cli/command/app/run.go +++ b/cmd/cli/command/app/run.go @@ -250,10 +250,10 @@ type ModuleDepFunc func(*moduleDeps) error func getServerModules(deps *moduleDeps) []app.ServerModuleFactory { return []app.ServerModuleFactory{ + module.LifecycleModuleFactory(), module.ContextModuleFactory(), module.ConsoleModuleFactory(), cast.CastModuleFactory(), - module.LifecycleModuleFactory(), netModule.ModuleFactory(deps.Bus), module.RPCModuleFactory(deps.Bus), module.StoreModuleFactory(deps.DocumentStore), diff --git a/pkg/app/promise_proxy.go b/pkg/app/promise_proxy.go index b9a1b85..2031be1 100644 --- a/pkg/app/promise_proxy.go +++ b/pkg/app/promise_proxy.go @@ -39,3 +39,14 @@ func NewPromiseProxy(promise *goja.Promise, resolve func(result interface{}), re return proxy } + +func NewPromiseProxyFrom(rt *goja.Runtime) *PromiseProxy { + promise, resolve, reject := rt.NewPromise() + + return NewPromiseProxy(promise, resolve, reject) +} + +func IsPromise(v goja.Value) (*goja.Promise, bool) { + promise, ok := v.Export().(*goja.Promise) + return promise, ok +} diff --git a/pkg/app/server.go b/pkg/app/server.go index 1c0f661..9df68dd 100644 --- a/pkg/app/server.go +++ b/pkg/app/server.go @@ -17,15 +17,22 @@ var ( ) type Server struct { - runtime *goja.Runtime - loop *eventloop.EventLoop - modules []ServerModule + loop *eventloop.EventLoop + factories []ServerModuleFactory + modules []ServerModule } func (s *Server) Load(name string, src string) error { - _, err := s.runtime.RunScript(name, src) + var err error + + s.loop.RunOnLoop(func(rt *goja.Runtime) { + _, err = rt.RunScript(name, src) + if err != nil { + err = errors.Wrap(err, "could not run js script") + } + }) if err != nil { - return errors.Wrap(err, "could not run js script") + return errors.WithStack(err) } return nil @@ -34,15 +41,15 @@ func (s *Server) Load(name string, src string) error { func (s *Server) ExecFuncByName(ctx context.Context, funcName string, args ...interface{}) (goja.Value, error) { ctx = logger.With(ctx, logger.F("function", funcName), logger.F("args", args)) - callable, ok := goja.AssertFunction(s.runtime.Get(funcName)) - if !ok { - return nil, errors.WithStack(ErrFuncDoesNotExist) + ret, err := s.Exec(ctx, funcName, args...) + if err != nil { + return nil, errors.WithStack(err) } - return s.Exec(ctx, callable, args...) + return ret, nil } -func (s *Server) Exec(ctx context.Context, callable goja.Callable, args ...interface{}) (goja.Value, error) { +func (s *Server) Exec(ctx context.Context, callableOrFuncname any, args ...interface{}) (goja.Value, error) { var ( wg sync.WaitGroup value goja.Value @@ -51,7 +58,28 @@ func (s *Server) Exec(ctx context.Context, callable goja.Callable, args ...inter wg.Add(1) - s.loop.RunOnLoop(func(vm *goja.Runtime) { + s.loop.RunOnLoop(func(rt *goja.Runtime) { + var callable goja.Callable + switch typ := callableOrFuncname.(type) { + case goja.Callable: + callable = typ + + case string: + call, ok := goja.AssertFunction(rt.Get(typ)) + if !ok { + err = errors.WithStack(ErrFuncDoesNotExist) + + return + } + + callable = call + + default: + err = errors.Errorf("callableOrFuncname: expected callable or function name, got '%T'", callableOrFuncname) + + return + } + logger.Debug(ctx, "executing callable") defer wg.Done() @@ -73,7 +101,7 @@ func (s *Server) Exec(ctx context.Context, callable goja.Callable, args ...inter jsArgs := make([]goja.Value, 0, len(args)) for _, a := range args { - jsArgs = append(jsArgs, vm.ToValue(a)) + jsArgs = append(jsArgs, rt.ToValue(a)) } value, err = callable(nil, jsArgs...) @@ -84,12 +112,11 @@ func (s *Server) Exec(ctx context.Context, callable goja.Callable, args ...inter wg.Wait() - return value, err -} + if err != nil { + return nil, errors.WithStack(err) + } -func (s *Server) IsPromise(v goja.Value) (*goja.Promise, bool) { - promise, ok := v.Export().(*goja.Promise) - return promise, ok + return value, nil } func (s *Server) WaitForPromise(promise *goja.Promise) goja.Value { @@ -135,28 +162,21 @@ func (s *Server) WaitForPromise(promise *goja.Promise) goja.Value { return value } -func (s *Server) NewPromise() *PromiseProxy { - promise, resolve, reject := s.runtime.NewPromise() - - return NewPromiseProxy(promise, resolve, reject) -} - -func (s *Server) ToValue(v interface{}) goja.Value { - return s.runtime.ToValue(v) -} - func (s *Server) Start() error { s.loop.Start() - for _, mod := range s.modules { - initMod, ok := mod.(InitializableModule) - if !ok { - continue - } + var err error - if err := initMod.OnInit(); err != nil { - return errors.WithStack(err) + s.loop.RunOnLoop(func(rt *goja.Runtime) { + rt.SetFieldNameMapper(goja.TagFieldNameMapper("goja", true)) + rt.SetRandSource(createRandomSource()) + + if err = s.initModules(rt); err != nil { + err = errors.WithStack(err) } + }) + if err != nil { + return errors.WithStack(err) } return nil @@ -166,36 +186,46 @@ func (s *Server) Stop() { s.loop.Stop() } -func (s *Server) initModules(factories ...ServerModuleFactory) { - runtime := goja.New() +func (s *Server) initModules(rt *goja.Runtime) error { + modules := make([]ServerModule, 0, len(s.factories)) - runtime.SetFieldNameMapper(goja.TagFieldNameMapper("goja", true)) - runtime.SetRandSource(createRandomSource()) - - modules := make([]ServerModule, 0, len(factories)) - - for _, moduleFactory := range factories { + for _, moduleFactory := range s.factories { mod := moduleFactory(s) - export := runtime.NewObject() + + export := rt.NewObject() mod.Export(export) - runtime.Set(mod.Name(), export) + + rt.Set(mod.Name(), export) modules = append(modules, mod) } - s.runtime = runtime + for _, mod := range modules { + initMod, ok := mod.(InitializableModule) + if !ok { + continue + } + + logger.Debug(context.Background(), "initializing module", logger.F("module", initMod.Name())) + + if err := initMod.OnInit(rt); err != nil { + return errors.WithStack(err) + } + } + s.modules = modules + + return nil } func NewServer(factories ...ServerModuleFactory) *Server { server := &Server{ + factories: factories, loop: eventloop.NewEventLoop( eventloop.EnableConsole(false), ), } - server.initModules(factories...) - return server } diff --git a/pkg/app/server_module.go b/pkg/app/server_module.go index d331bf2..03f7f25 100644 --- a/pkg/app/server_module.go +++ b/pkg/app/server_module.go @@ -13,5 +13,5 @@ type ServerModule interface { type InitializableModule interface { ServerModule - OnInit() error + OnInit(rt *goja.Runtime) error } diff --git a/pkg/module/auth/module.go b/pkg/module/auth/module.go index ffa8d8e..495a729 100644 --- a/pkg/module/auth/module.go +++ b/pkg/module/auth/module.go @@ -53,6 +53,10 @@ func (m *Module) Export(export *goja.Object) { if err := export.Set("CLAIM_PREFERRED_USERNAME", ClaimPreferredUsername); err != nil { panic(errors.Wrap(err, "could not set 'CLAIM_PREFERRED_USERNAME' property")) } + + if err := export.Set("CLAIM_ISSUER", ClaimIssuer); err != nil { + panic(errors.Wrap(err, "could not set 'CLAIM_ISSUER' property")) + } } func (m *Module) getClaim(call goja.FunctionCall, rt *goja.Runtime) goja.Value { diff --git a/pkg/module/cast/module.go b/pkg/module/cast/module.go index bfc53e2..1344cac 100644 --- a/pkg/module/cast/module.go +++ b/pkg/module/cast/module.go @@ -15,10 +15,7 @@ const ( defaultTimeout = 30 * time.Second ) -type Module struct { - ctx context.Context - server *app.Server -} +type Module struct{} func (m *Module) Name() string { return "cast" @@ -54,7 +51,7 @@ func (m *Module) refreshDevices(call goja.FunctionCall, rt *goja.Runtime) goja.V panic(rt.ToValue(errors.WithStack(err))) } - promise := m.server.NewPromise() + promise := app.NewPromiseProxyFrom(rt) go func() { ctx, cancel := context.WithTimeout(context.Background(), timeout) @@ -102,7 +99,7 @@ func (m *Module) loadUrl(call goja.FunctionCall, rt *goja.Runtime) goja.Value { panic(rt.ToValue(errors.WithStack(err))) } - promise := m.server.NewPromise() + promise := app.NewPromiseProxyFrom(rt) go func() { ctx, cancel := context.WithTimeout(context.Background(), timeout) @@ -121,7 +118,7 @@ func (m *Module) loadUrl(call goja.FunctionCall, rt *goja.Runtime) goja.Value { promise.Resolve(nil) }() - return m.server.ToValue(promise) + return rt.ToValue(promise) } func (m *Module) stopCast(call goja.FunctionCall, rt *goja.Runtime) goja.Value { @@ -137,7 +134,7 @@ func (m *Module) stopCast(call goja.FunctionCall, rt *goja.Runtime) goja.Value { panic(rt.ToValue(errors.WithStack(err))) } - promise := m.server.NewPromise() + promise := app.NewPromiseProxyFrom(rt) go func() { ctx, cancel := context.WithTimeout(context.Background(), timeout) @@ -156,7 +153,7 @@ func (m *Module) stopCast(call goja.FunctionCall, rt *goja.Runtime) goja.Value { promise.Resolve(nil) }() - return m.server.ToValue(promise) + return rt.ToValue(promise) } func (m *Module) getStatus(call goja.FunctionCall, rt *goja.Runtime) goja.Value { @@ -172,7 +169,7 @@ func (m *Module) getStatus(call goja.FunctionCall, rt *goja.Runtime) goja.Value panic(rt.ToValue(errors.WithStack(err))) } - promise := m.server.NewPromise() + promise := app.NewPromiseProxyFrom(rt) go func() { ctx, cancel := context.WithTimeout(context.Background(), timeout) @@ -191,7 +188,7 @@ func (m *Module) getStatus(call goja.FunctionCall, rt *goja.Runtime) goja.Value promise.Resolve(status) }() - return m.server.ToValue(promise) + return rt.ToValue(promise) } func (m *Module) parseTimeout(rawTimeout string) (time.Duration, error) { @@ -214,8 +211,6 @@ func (m *Module) parseTimeout(rawTimeout string) (time.Duration, error) { func CastModuleFactory() app.ServerModuleFactory { return func(server *app.Server) app.ServerModule { - return &Module{ - server: server, - } + return &Module{} } } diff --git a/pkg/module/cast/module_test.go b/pkg/module/cast/module_test.go index 01c0a1a..dfdc041 100644 --- a/pkg/module/cast/module_test.go +++ b/pkg/module/cast/module_test.go @@ -85,7 +85,7 @@ func TestCastModuleRefreshDevices(t *testing.T) { t.Error(errors.WithStack(err)) } - promise, ok := server.IsPromise(result) + promise, ok := app.IsPromise(result) if !ok { t.Fatal("expected promise") } diff --git a/pkg/module/lifecycle.go b/pkg/module/lifecycle.go index 2a23e4a..5bc6b6d 100644 --- a/pkg/module/lifecycle.go +++ b/pkg/module/lifecycle.go @@ -9,9 +9,7 @@ import ( "gitlab.com/wpetit/goweb/logger" ) -type LifecycleModule struct { - server *app.Server -} +type LifecycleModule struct{} func (m *LifecycleModule) Name() string { return "lifecycle" @@ -20,25 +18,37 @@ func (m *LifecycleModule) Name() string { func (m *LifecycleModule) Export(export *goja.Object) { } -func (m *LifecycleModule) OnInit() error { - if _, err := m.server.ExecFuncByName(context.Background(), "onInit"); err != nil { - if errors.Is(err, app.ErrFuncDoesNotExist) { - logger.Warn(context.Background(), "could not find onInit() function", logger.E(errors.WithStack(err))) +func (m *LifecycleModule) OnInit(rt *goja.Runtime) (err error) { + call, ok := goja.AssertFunction(rt.Get("onInit")) + if !ok { + logger.Warn(context.Background(), "could not find onInit() function") - return nil - } - - return errors.WithStack(err) + return nil } + defer func() { + if recovered := recover(); recovered != nil { + revoveredErr, ok := recovered.(error) + if ok { + logger.Error(context.Background(), "recovered runtime error", logger.E(errors.WithStack(revoveredErr))) + + err = errors.WithStack(app.ErUnknownError) + + return + } + + panic(recovered) + } + }() + + call(nil) + return nil } func LifecycleModuleFactory() app.ServerModuleFactory { return func(server *app.Server) app.ServerModule { - module := &LifecycleModule{ - server: server, - } + module := &LifecycleModule{} return module } diff --git a/pkg/module/rpc.go b/pkg/module/rpc.go index 732d9af..1ced983 100644 --- a/pkg/module/rpc.go +++ b/pkg/module/rpc.go @@ -51,6 +51,12 @@ func (m *RPCModule) Export(export *goja.Object) { } } +func (m *RPCModule) OnInit(rt *goja.Runtime) error { + go m.handleMessages() + + return nil +} + func (m *RPCModule) register(call goja.FunctionCall, rt *goja.Runtime) goja.Value { fnName := util.AssertString(call.Argument(0), rt) @@ -181,7 +187,7 @@ func (m *RPCModule) handleMessages() { continue } - promise, ok := m.server.IsPromise(result) + promise, ok := app.IsPromise(result) if ok { go func(ctx context.Context, req *RPCRequest, promise *goja.Promise) { result := m.server.WaitForPromise(promise) @@ -263,8 +269,8 @@ func RPCModuleFactory(bus bus.Bus) app.ServerModuleFactory { bus: bus, } - go mod.handleMessages() - return mod } } + +var _ app.InitializableModule = &RPCModule{}