Loading CHANGELOG.md +1 −0 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr - Filtering by group state in user groups listing operations. - Allow max count to be set when creating groups from client calls. - Log better startup error message when database schema is not set up at all. - New `check` command to validate runtime modules without starting the server. ### Changed - Use Go 1.12.9 on Alpine 3.10 as base Docker container image and native builds. Loading main.go +18 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package main import ( "context" "database/sql" "flag" "fmt" "math/rand" "net/http" Loading Loading @@ -76,6 +77,23 @@ func main() { return case "migrate": migrate.Parse(os.Args[2:], tmpLogger) case "check": // Parse any command line args to look up runtime path. // Use full config structure even if not all of its options are available in this command. config := server.NewConfig(tmpLogger) var runtimePath string flags := flag.NewFlagSet("check", flag.ExitOnError) flags.StringVar(&runtimePath, "runtime.path", filepath.Join(config.GetDataDir(), "modules"), "Path for the server to scan for Lua and Go library files.") if err := flags.Parse(os.Args[2:]); err != nil { tmpLogger.Fatal("Could not parse check flags.") } config.GetRuntime().Path = runtimePath if err := server.CheckRuntime(tmpLogger, config); err != nil { // Errors are already logged in the function above. os.Exit(1) } return } } Loading server/runtime.go +39 −8 Original line number Diff line number Diff line Loading @@ -378,18 +378,15 @@ type Runtime struct { eventFunctions *RuntimeEventFunctions } func NewRuntime(logger, startupLogger *zap.Logger, db *sql.DB, jsonpbMarshaler *jsonpb.Marshaler, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, leaderboardRankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter) (*Runtime, error) { runtimeConfig := config.GetRuntime() startupLogger.Info("Initialising runtime", zap.String("path", runtimeConfig.Path)) if err := os.MkdirAll(runtimeConfig.Path, os.ModePerm); err != nil { func GetRuntimePaths(logger *zap.Logger, rootPath string) ([]string, error) { if err := os.MkdirAll(rootPath, os.ModePerm); err != nil { return nil, err } paths := make([]string, 0) if err := filepath.Walk(runtimeConfig.Path, func(path string, f os.FileInfo, err error) error { if err := filepath.Walk(rootPath, func(path string, f os.FileInfo, err error) error { if err != nil { startupLogger.Error("Error listing runtime path", zap.String("path", path), zap.Error(err)) logger.Error("Error listing runtime path", zap.String("path", path), zap.Error(err)) return err } Loading @@ -399,7 +396,41 @@ func NewRuntime(logger, startupLogger *zap.Logger, db *sql.DB, jsonpbMarshaler * } return nil }); err != nil { startupLogger.Error("Failed to list runtime path", zap.Error(err)) logger.Error("Failed to list runtime path", zap.Error(err)) return nil, err } return paths, nil } func CheckRuntime(logger *zap.Logger, config Config) error { // Get all paths inside the configured runtime. paths, err := GetRuntimePaths(logger, config.GetRuntime().Path) if err != nil { return err } // Check any Go runtime modules. err = CheckRuntimeProviderGo(logger, config.GetRuntime().Path, paths) if err != nil { return err } // Check any Lua runtime modules. err = CheckRuntimeProviderLua(logger, config, paths) if err != nil { return err } return nil } func NewRuntime(logger, startupLogger *zap.Logger, db *sql.DB, jsonpbMarshaler *jsonpb.Marshaler, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, leaderboardRankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter) (*Runtime, error) { runtimeConfig := config.GetRuntime() startupLogger.Info("Initialising runtime", zap.String("path", runtimeConfig.Path)) paths, err := GetRuntimePaths(startupLogger, runtimeConfig.Path) if err != nil { return nil, err } Loading server/runtime_go.go +51 −20 Original line number Diff line number Diff line Loading @@ -1812,34 +1812,18 @@ func NewRuntimeProviderGo(logger, startupLogger *zap.Logger, db *sql.DB, config modulePaths := make([]string, 0) for _, path := range paths { // Skip everything except shared object files. if strings.ToLower(filepath.Ext(path)) != ".so" { continue } relPath, _ := filepath.Rel(rootPath, path) name := strings.TrimSuffix(relPath, filepath.Ext(relPath)) // Open the plugin. p, err := plugin.Open(path) if err != nil { startupLogger.Error("Could not open Go module", zap.String("path", path), zap.Error(err)) return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err } // Look up the required initialisation function. f, err := p.Lookup("InitModule") // Open the plugin, and look up the required initialisation function. relPath, name, fn, err := openGoModule(startupLogger, rootPath, path) if err != nil { startupLogger.Fatal("Error looking up InitModule function in Go module", zap.String("name", name)) // Errors are already logged in the function above. return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err } // Ensure the function has the correct signature. fn, ok := f.(func(context.Context, runtime.Logger, *sql.DB, runtime.NakamaModule, runtime.Initializer) error) if !ok { startupLogger.Fatal("Error reading InitModule function in Go module", zap.String("name", name)) return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, errors.New("error reading InitModule function in Go module") } // Run the initialisation. if err = fn(ctx, runtimeLogger, db, nk, initializer); err != nil { startupLogger.Fatal("Error returned by InitModule function in Go module", zap.String("name", name), zap.Error(err)) Loading Loading @@ -1883,3 +1867,50 @@ func NewRuntimeProviderGo(logger, startupLogger *zap.Logger, db *sql.DB, config return modulePaths, initializer.rpc, initializer.beforeRt, initializer.afterRt, initializer.beforeReq, initializer.afterReq, initializer.matchmakerMatched, matchCreateFn, initializer.tournamentEnd, initializer.tournamentReset, initializer.leaderboardReset, events, nk.SetMatchCreateFn, matchNamesListFn, nil } func CheckRuntimeProviderGo(logger *zap.Logger, rootPath string, paths []string) error { for _, path := range paths { // Skip everything except shared object files. if strings.ToLower(filepath.Ext(path)) != ".so" { continue } // Open the plugin, and look up the required initialisation function. // The function isn't used here, all we need is a type/signature check. _, _, _, err := openGoModule(logger, rootPath, path) if err != nil { // Errors are already logged in the function above. return err } } return nil } func openGoModule(logger *zap.Logger, rootPath, path string) (string, string, func(context.Context, runtime.Logger, *sql.DB, runtime.NakamaModule, runtime.Initializer) error, error) { relPath, _ := filepath.Rel(rootPath, path) name := strings.TrimSuffix(relPath, filepath.Ext(relPath)) // Open the plugin. p, err := plugin.Open(path) if err != nil { logger.Error("Could not open Go module", zap.String("path", path), zap.Error(err)) return "", "", nil, err } // Look up the required initialisation function. f, err := p.Lookup("InitModule") if err != nil { logger.Fatal("Error looking up InitModule function in Go module", zap.String("name", name)) return "", "", nil, err } // Ensure the function has the correct signature. fn, ok := f.(func(context.Context, runtime.Logger, *sql.DB, runtime.NakamaModule, runtime.Initializer) error) if !ok { logger.Fatal("Error reading InitModule function in Go module", zap.String("name", name)) return "", "", nil, errors.New("error reading InitModule function in Go module") } return relPath, name, fn, nil } server/runtime_lua.go +112 −48 Original line number Diff line number Diff line Loading @@ -108,57 +108,15 @@ type RuntimeProviderLua struct { } func NewRuntimeProviderLua(logger, startupLogger *zap.Logger, db *sql.DB, jsonpbMarshaler *jsonpb.Marshaler, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, leaderboardRankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter, goMatchCreateFn RuntimeMatchCreateFunction, rootPath string, paths []string) ([]string, map[string]RuntimeRpcFunction, map[string]RuntimeBeforeRtFunction, map[string]RuntimeAfterRtFunction, *RuntimeBeforeReqFunctions, *RuntimeAfterReqFunctions, RuntimeMatchmakerMatchedFunction, RuntimeMatchCreateFunction, RuntimeTournamentEndFunction, RuntimeTournamentResetFunction, RuntimeLeaderboardResetFunction, error) { moduleCache := &RuntimeLuaModuleCache{ Names: make([]string, 0), Modules: make(map[string]*RuntimeLuaModule, 0), } modulePaths := make([]string, 0) // Override before Package library is invoked. lua.LuaLDir = rootPath lua.LuaPathDefault = lua.LuaLDir + string(os.PathSeparator) + "?.lua;" + lua.LuaLDir + string(os.PathSeparator) + "?" + string(os.PathSeparator) + "init.lua" if err := os.Setenv(lua.LuaPath, lua.LuaPathDefault); err != nil { startupLogger.Error("Could not set Lua module path", zap.Error(err)) return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err } startupLogger.Info("Initialising Lua runtime provider", zap.String("path", rootPath)) for _, path := range paths { if strings.ToLower(filepath.Ext(path)) != ".lua" { continue } // Load the file contents into memory. var content []byte var err error if content, err = ioutil.ReadFile(path); err != nil { startupLogger.Error("Could not read Lua module", zap.String("path", path), zap.Error(err)) // Load Lua modules into memory by reading the file contents. No evaluation/execution at this stage. moduleCache, modulePaths, stdLibs, err := openLuaModules(startupLogger, rootPath, paths) if err != nil { // Errors already logged in the function call above. return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err } relPath, _ := filepath.Rel(rootPath, path) name := strings.TrimSuffix(relPath, filepath.Ext(relPath)) // Make paths Lua friendly. name = strings.Replace(name, string(os.PathSeparator), ".", -1) moduleCache.Add(&RuntimeLuaModule{ Name: name, Path: path, Content: content, }) modulePaths = append(modulePaths, relPath) } stdLibs := map[string]lua.LGFunction{ lua.LoadLibName: OpenPackage(moduleCache), lua.BaseLibName: lua.OpenBase, lua.TabLibName: lua.OpenTable, lua.OsLibName: OpenOs, lua.StringLibName: lua.OpenString, lua.MathLibName: lua.OpenMath, Bit32LibName: OpenBit32, } once := &sync.Once{} localCache := NewRuntimeLuaLocalCache() rpcFunctions := make(map[string]RuntimeRpcFunction, 0) Loading Loading @@ -962,6 +920,78 @@ func NewRuntimeProviderLua(logger, startupLogger *zap.Logger, db *sql.DB, jsonpb return modulePaths, rpcFunctions, beforeRtFunctions, afterRtFunctions, beforeReqFunctions, afterReqFunctions, matchmakerMatchedFunction, allMatchCreateFn, tournamentEndFunction, tournamentResetFunction, leaderboardResetFunction, nil } func CheckRuntimeProviderLua(logger *zap.Logger, config Config, paths []string) error { // Load Lua modules into memory by reading the file contents. No evaluation/execution at this stage. moduleCache, _, stdLibs, err := openLuaModules(logger, config.GetRuntime().Path, paths) if err != nil { // Errors already logged in the function call above. return err } // Evaluate (but do not execute) available Lua modules. err = checkRuntimeLuaVM(logger, config, stdLibs, moduleCache) if err != nil { // Errors already logged in the function call above. return err } return nil } func openLuaModules(logger *zap.Logger, rootPath string, paths []string) (*RuntimeLuaModuleCache, []string, map[string]lua.LGFunction, error) { moduleCache := &RuntimeLuaModuleCache{ Names: make([]string, 0), Modules: make(map[string]*RuntimeLuaModule, 0), } modulePaths := make([]string, 0) // Override before Package library is invoked. lua.LuaLDir = rootPath lua.LuaPathDefault = lua.LuaLDir + string(os.PathSeparator) + "?.lua;" + lua.LuaLDir + string(os.PathSeparator) + "?" + string(os.PathSeparator) + "init.lua" if err := os.Setenv(lua.LuaPath, lua.LuaPathDefault); err != nil { logger.Error("Could not set Lua module path", zap.Error(err)) return nil, nil, nil, err } for _, path := range paths { if strings.ToLower(filepath.Ext(path)) != ".lua" { continue } // Load the file contents into memory. var content []byte var err error if content, err = ioutil.ReadFile(path); err != nil { logger.Error("Could not read Lua module", zap.String("path", path), zap.Error(err)) return nil, nil, nil, err } relPath, _ := filepath.Rel(rootPath, path) name := strings.TrimSuffix(relPath, filepath.Ext(relPath)) // Make paths Lua friendly. name = strings.Replace(name, string(os.PathSeparator), ".", -1) moduleCache.Add(&RuntimeLuaModule{ Name: name, Path: path, Content: content, }) modulePaths = append(modulePaths, relPath) } stdLibs := map[string]lua.LGFunction{ lua.LoadLibName: OpenPackage(moduleCache), lua.BaseLibName: lua.OpenBase, lua.TabLibName: lua.OpenTable, lua.OsLibName: OpenOs, lua.StringLibName: lua.OpenString, lua.MathLibName: lua.OpenMath, Bit32LibName: OpenBit32, } return moduleCache, modulePaths, stdLibs, nil } func (rp *RuntimeProviderLua) Rpc(ctx context.Context, id string, queryParams map[string][]string, userID, username string, expiry int64, sessionID, clientIP, clientPort, payload string) (string, error, codes.Code) { r, err := rp.Get(ctx) if err != nil { Loading Loading @@ -1752,8 +1782,42 @@ func (r *RuntimeLua) Stop() { r.vm.Close() } func checkRuntimeLuaVM(logger *zap.Logger, config Config, stdLibs map[string]lua.LGFunction, moduleCache *RuntimeLuaModuleCache) error { vm := lua.NewState(lua.Options{ CallStackSize: 128, RegistrySize: 512, SkipOpenLibs: true, IncludeGoStackTrace: true, }) vm.SetContext(context.Background()) for name, lib := range stdLibs { vm.Push(vm.NewFunction(lib)) vm.Push(lua.LString(name)) vm.Call(1, 0) } nakamaModule := NewRuntimeLuaNakamaModule(nil, nil, nil, config, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) vm.PreloadModule("nakama", nakamaModule.Loader) preload := vm.GetField(vm.GetField(vm.Get(lua.EnvironIndex), "package"), "preload") for _, name := range moduleCache.Names { module, ok := moduleCache.Modules[name] if !ok { logger.Fatal("Failed to find named module in cache", zap.String("name", name)) } f, err := vm.Load(bytes.NewReader(module.Content), module.Path) if err != nil { logger.Error("Could not load module", zap.String("name", module.Path), zap.Error(err)) return err } else { vm.SetField(preload, module.Name, f) } } return nil } func newRuntimeLuaVM(logger *zap.Logger, db *sql.DB, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, rankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter, stdLibs map[string]lua.LGFunction, moduleCache *RuntimeLuaModuleCache, once *sync.Once, localCache *RuntimeLuaLocalCache, matchCreateFn RuntimeMatchCreateFunction, announceCallbackFn func(RuntimeExecutionMode, string)) (*RuntimeLua, error) { // Initialize a one-off runtime to ensure startup code runs and modules are valid. vm := lua.NewState(lua.Options{ CallStackSize: config.GetRuntime().CallStackSize, RegistrySize: config.GetRuntime().RegistrySize, Loading Loading
CHANGELOG.md +1 −0 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr - Filtering by group state in user groups listing operations. - Allow max count to be set when creating groups from client calls. - Log better startup error message when database schema is not set up at all. - New `check` command to validate runtime modules without starting the server. ### Changed - Use Go 1.12.9 on Alpine 3.10 as base Docker container image and native builds. Loading
main.go +18 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package main import ( "context" "database/sql" "flag" "fmt" "math/rand" "net/http" Loading Loading @@ -76,6 +77,23 @@ func main() { return case "migrate": migrate.Parse(os.Args[2:], tmpLogger) case "check": // Parse any command line args to look up runtime path. // Use full config structure even if not all of its options are available in this command. config := server.NewConfig(tmpLogger) var runtimePath string flags := flag.NewFlagSet("check", flag.ExitOnError) flags.StringVar(&runtimePath, "runtime.path", filepath.Join(config.GetDataDir(), "modules"), "Path for the server to scan for Lua and Go library files.") if err := flags.Parse(os.Args[2:]); err != nil { tmpLogger.Fatal("Could not parse check flags.") } config.GetRuntime().Path = runtimePath if err := server.CheckRuntime(tmpLogger, config); err != nil { // Errors are already logged in the function above. os.Exit(1) } return } } Loading
server/runtime.go +39 −8 Original line number Diff line number Diff line Loading @@ -378,18 +378,15 @@ type Runtime struct { eventFunctions *RuntimeEventFunctions } func NewRuntime(logger, startupLogger *zap.Logger, db *sql.DB, jsonpbMarshaler *jsonpb.Marshaler, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, leaderboardRankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter) (*Runtime, error) { runtimeConfig := config.GetRuntime() startupLogger.Info("Initialising runtime", zap.String("path", runtimeConfig.Path)) if err := os.MkdirAll(runtimeConfig.Path, os.ModePerm); err != nil { func GetRuntimePaths(logger *zap.Logger, rootPath string) ([]string, error) { if err := os.MkdirAll(rootPath, os.ModePerm); err != nil { return nil, err } paths := make([]string, 0) if err := filepath.Walk(runtimeConfig.Path, func(path string, f os.FileInfo, err error) error { if err := filepath.Walk(rootPath, func(path string, f os.FileInfo, err error) error { if err != nil { startupLogger.Error("Error listing runtime path", zap.String("path", path), zap.Error(err)) logger.Error("Error listing runtime path", zap.String("path", path), zap.Error(err)) return err } Loading @@ -399,7 +396,41 @@ func NewRuntime(logger, startupLogger *zap.Logger, db *sql.DB, jsonpbMarshaler * } return nil }); err != nil { startupLogger.Error("Failed to list runtime path", zap.Error(err)) logger.Error("Failed to list runtime path", zap.Error(err)) return nil, err } return paths, nil } func CheckRuntime(logger *zap.Logger, config Config) error { // Get all paths inside the configured runtime. paths, err := GetRuntimePaths(logger, config.GetRuntime().Path) if err != nil { return err } // Check any Go runtime modules. err = CheckRuntimeProviderGo(logger, config.GetRuntime().Path, paths) if err != nil { return err } // Check any Lua runtime modules. err = CheckRuntimeProviderLua(logger, config, paths) if err != nil { return err } return nil } func NewRuntime(logger, startupLogger *zap.Logger, db *sql.DB, jsonpbMarshaler *jsonpb.Marshaler, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, leaderboardRankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter) (*Runtime, error) { runtimeConfig := config.GetRuntime() startupLogger.Info("Initialising runtime", zap.String("path", runtimeConfig.Path)) paths, err := GetRuntimePaths(startupLogger, runtimeConfig.Path) if err != nil { return nil, err } Loading
server/runtime_go.go +51 −20 Original line number Diff line number Diff line Loading @@ -1812,34 +1812,18 @@ func NewRuntimeProviderGo(logger, startupLogger *zap.Logger, db *sql.DB, config modulePaths := make([]string, 0) for _, path := range paths { // Skip everything except shared object files. if strings.ToLower(filepath.Ext(path)) != ".so" { continue } relPath, _ := filepath.Rel(rootPath, path) name := strings.TrimSuffix(relPath, filepath.Ext(relPath)) // Open the plugin. p, err := plugin.Open(path) if err != nil { startupLogger.Error("Could not open Go module", zap.String("path", path), zap.Error(err)) return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err } // Look up the required initialisation function. f, err := p.Lookup("InitModule") // Open the plugin, and look up the required initialisation function. relPath, name, fn, err := openGoModule(startupLogger, rootPath, path) if err != nil { startupLogger.Fatal("Error looking up InitModule function in Go module", zap.String("name", name)) // Errors are already logged in the function above. return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err } // Ensure the function has the correct signature. fn, ok := f.(func(context.Context, runtime.Logger, *sql.DB, runtime.NakamaModule, runtime.Initializer) error) if !ok { startupLogger.Fatal("Error reading InitModule function in Go module", zap.String("name", name)) return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, errors.New("error reading InitModule function in Go module") } // Run the initialisation. if err = fn(ctx, runtimeLogger, db, nk, initializer); err != nil { startupLogger.Fatal("Error returned by InitModule function in Go module", zap.String("name", name), zap.Error(err)) Loading Loading @@ -1883,3 +1867,50 @@ func NewRuntimeProviderGo(logger, startupLogger *zap.Logger, db *sql.DB, config return modulePaths, initializer.rpc, initializer.beforeRt, initializer.afterRt, initializer.beforeReq, initializer.afterReq, initializer.matchmakerMatched, matchCreateFn, initializer.tournamentEnd, initializer.tournamentReset, initializer.leaderboardReset, events, nk.SetMatchCreateFn, matchNamesListFn, nil } func CheckRuntimeProviderGo(logger *zap.Logger, rootPath string, paths []string) error { for _, path := range paths { // Skip everything except shared object files. if strings.ToLower(filepath.Ext(path)) != ".so" { continue } // Open the plugin, and look up the required initialisation function. // The function isn't used here, all we need is a type/signature check. _, _, _, err := openGoModule(logger, rootPath, path) if err != nil { // Errors are already logged in the function above. return err } } return nil } func openGoModule(logger *zap.Logger, rootPath, path string) (string, string, func(context.Context, runtime.Logger, *sql.DB, runtime.NakamaModule, runtime.Initializer) error, error) { relPath, _ := filepath.Rel(rootPath, path) name := strings.TrimSuffix(relPath, filepath.Ext(relPath)) // Open the plugin. p, err := plugin.Open(path) if err != nil { logger.Error("Could not open Go module", zap.String("path", path), zap.Error(err)) return "", "", nil, err } // Look up the required initialisation function. f, err := p.Lookup("InitModule") if err != nil { logger.Fatal("Error looking up InitModule function in Go module", zap.String("name", name)) return "", "", nil, err } // Ensure the function has the correct signature. fn, ok := f.(func(context.Context, runtime.Logger, *sql.DB, runtime.NakamaModule, runtime.Initializer) error) if !ok { logger.Fatal("Error reading InitModule function in Go module", zap.String("name", name)) return "", "", nil, errors.New("error reading InitModule function in Go module") } return relPath, name, fn, nil }
server/runtime_lua.go +112 −48 Original line number Diff line number Diff line Loading @@ -108,57 +108,15 @@ type RuntimeProviderLua struct { } func NewRuntimeProviderLua(logger, startupLogger *zap.Logger, db *sql.DB, jsonpbMarshaler *jsonpb.Marshaler, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, leaderboardRankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter, goMatchCreateFn RuntimeMatchCreateFunction, rootPath string, paths []string) ([]string, map[string]RuntimeRpcFunction, map[string]RuntimeBeforeRtFunction, map[string]RuntimeAfterRtFunction, *RuntimeBeforeReqFunctions, *RuntimeAfterReqFunctions, RuntimeMatchmakerMatchedFunction, RuntimeMatchCreateFunction, RuntimeTournamentEndFunction, RuntimeTournamentResetFunction, RuntimeLeaderboardResetFunction, error) { moduleCache := &RuntimeLuaModuleCache{ Names: make([]string, 0), Modules: make(map[string]*RuntimeLuaModule, 0), } modulePaths := make([]string, 0) // Override before Package library is invoked. lua.LuaLDir = rootPath lua.LuaPathDefault = lua.LuaLDir + string(os.PathSeparator) + "?.lua;" + lua.LuaLDir + string(os.PathSeparator) + "?" + string(os.PathSeparator) + "init.lua" if err := os.Setenv(lua.LuaPath, lua.LuaPathDefault); err != nil { startupLogger.Error("Could not set Lua module path", zap.Error(err)) return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err } startupLogger.Info("Initialising Lua runtime provider", zap.String("path", rootPath)) for _, path := range paths { if strings.ToLower(filepath.Ext(path)) != ".lua" { continue } // Load the file contents into memory. var content []byte var err error if content, err = ioutil.ReadFile(path); err != nil { startupLogger.Error("Could not read Lua module", zap.String("path", path), zap.Error(err)) // Load Lua modules into memory by reading the file contents. No evaluation/execution at this stage. moduleCache, modulePaths, stdLibs, err := openLuaModules(startupLogger, rootPath, paths) if err != nil { // Errors already logged in the function call above. return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err } relPath, _ := filepath.Rel(rootPath, path) name := strings.TrimSuffix(relPath, filepath.Ext(relPath)) // Make paths Lua friendly. name = strings.Replace(name, string(os.PathSeparator), ".", -1) moduleCache.Add(&RuntimeLuaModule{ Name: name, Path: path, Content: content, }) modulePaths = append(modulePaths, relPath) } stdLibs := map[string]lua.LGFunction{ lua.LoadLibName: OpenPackage(moduleCache), lua.BaseLibName: lua.OpenBase, lua.TabLibName: lua.OpenTable, lua.OsLibName: OpenOs, lua.StringLibName: lua.OpenString, lua.MathLibName: lua.OpenMath, Bit32LibName: OpenBit32, } once := &sync.Once{} localCache := NewRuntimeLuaLocalCache() rpcFunctions := make(map[string]RuntimeRpcFunction, 0) Loading Loading @@ -962,6 +920,78 @@ func NewRuntimeProviderLua(logger, startupLogger *zap.Logger, db *sql.DB, jsonpb return modulePaths, rpcFunctions, beforeRtFunctions, afterRtFunctions, beforeReqFunctions, afterReqFunctions, matchmakerMatchedFunction, allMatchCreateFn, tournamentEndFunction, tournamentResetFunction, leaderboardResetFunction, nil } func CheckRuntimeProviderLua(logger *zap.Logger, config Config, paths []string) error { // Load Lua modules into memory by reading the file contents. No evaluation/execution at this stage. moduleCache, _, stdLibs, err := openLuaModules(logger, config.GetRuntime().Path, paths) if err != nil { // Errors already logged in the function call above. return err } // Evaluate (but do not execute) available Lua modules. err = checkRuntimeLuaVM(logger, config, stdLibs, moduleCache) if err != nil { // Errors already logged in the function call above. return err } return nil } func openLuaModules(logger *zap.Logger, rootPath string, paths []string) (*RuntimeLuaModuleCache, []string, map[string]lua.LGFunction, error) { moduleCache := &RuntimeLuaModuleCache{ Names: make([]string, 0), Modules: make(map[string]*RuntimeLuaModule, 0), } modulePaths := make([]string, 0) // Override before Package library is invoked. lua.LuaLDir = rootPath lua.LuaPathDefault = lua.LuaLDir + string(os.PathSeparator) + "?.lua;" + lua.LuaLDir + string(os.PathSeparator) + "?" + string(os.PathSeparator) + "init.lua" if err := os.Setenv(lua.LuaPath, lua.LuaPathDefault); err != nil { logger.Error("Could not set Lua module path", zap.Error(err)) return nil, nil, nil, err } for _, path := range paths { if strings.ToLower(filepath.Ext(path)) != ".lua" { continue } // Load the file contents into memory. var content []byte var err error if content, err = ioutil.ReadFile(path); err != nil { logger.Error("Could not read Lua module", zap.String("path", path), zap.Error(err)) return nil, nil, nil, err } relPath, _ := filepath.Rel(rootPath, path) name := strings.TrimSuffix(relPath, filepath.Ext(relPath)) // Make paths Lua friendly. name = strings.Replace(name, string(os.PathSeparator), ".", -1) moduleCache.Add(&RuntimeLuaModule{ Name: name, Path: path, Content: content, }) modulePaths = append(modulePaths, relPath) } stdLibs := map[string]lua.LGFunction{ lua.LoadLibName: OpenPackage(moduleCache), lua.BaseLibName: lua.OpenBase, lua.TabLibName: lua.OpenTable, lua.OsLibName: OpenOs, lua.StringLibName: lua.OpenString, lua.MathLibName: lua.OpenMath, Bit32LibName: OpenBit32, } return moduleCache, modulePaths, stdLibs, nil } func (rp *RuntimeProviderLua) Rpc(ctx context.Context, id string, queryParams map[string][]string, userID, username string, expiry int64, sessionID, clientIP, clientPort, payload string) (string, error, codes.Code) { r, err := rp.Get(ctx) if err != nil { Loading Loading @@ -1752,8 +1782,42 @@ func (r *RuntimeLua) Stop() { r.vm.Close() } func checkRuntimeLuaVM(logger *zap.Logger, config Config, stdLibs map[string]lua.LGFunction, moduleCache *RuntimeLuaModuleCache) error { vm := lua.NewState(lua.Options{ CallStackSize: 128, RegistrySize: 512, SkipOpenLibs: true, IncludeGoStackTrace: true, }) vm.SetContext(context.Background()) for name, lib := range stdLibs { vm.Push(vm.NewFunction(lib)) vm.Push(lua.LString(name)) vm.Call(1, 0) } nakamaModule := NewRuntimeLuaNakamaModule(nil, nil, nil, config, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) vm.PreloadModule("nakama", nakamaModule.Loader) preload := vm.GetField(vm.GetField(vm.Get(lua.EnvironIndex), "package"), "preload") for _, name := range moduleCache.Names { module, ok := moduleCache.Modules[name] if !ok { logger.Fatal("Failed to find named module in cache", zap.String("name", name)) } f, err := vm.Load(bytes.NewReader(module.Content), module.Path) if err != nil { logger.Error("Could not load module", zap.String("name", module.Path), zap.Error(err)) return err } else { vm.SetField(preload, module.Name, f) } } return nil } func newRuntimeLuaVM(logger *zap.Logger, db *sql.DB, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, rankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter, stdLibs map[string]lua.LGFunction, moduleCache *RuntimeLuaModuleCache, once *sync.Once, localCache *RuntimeLuaLocalCache, matchCreateFn RuntimeMatchCreateFunction, announceCallbackFn func(RuntimeExecutionMode, string)) (*RuntimeLua, error) { // Initialize a one-off runtime to ensure startup code runs and modules are valid. vm := lua.NewState(lua.Options{ CallStackSize: config.GetRuntime().CallStackSize, RegistrySize: config.GetRuntime().RegistrySize, Loading