Loading CHANGELOG.md +1 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr - Lua runtime AES-256 functions. - Lua runtime token generator function now returns a second value representing the token's expiry. - Add local cache for in-memory storage to the Lua runtime. - Graceful server shutdown and match termination. ### Changed - Improved Postgres compatibility on TIMESTAMPTZ types. Loading data/modules/match.lua +46 −1 Original line number Diff line number Diff line Loading @@ -262,11 +262,56 @@ local function match_loop(context, dispatcher, tick, state, messages) end end --[[ Called when the server begins a graceful shutdown process. Will not be called if graceful shutdown is disabled. Context represents information about the match and server, for information purposes. Format: { env = {}, -- key-value data set in the runtime.env server configuration. executionMode = "Match", match_id = "client-friendly match ID, can be shared with clients and used in match join operations", match_node = "name of the Nakama node hosting this match", match_label = "the label string returned from match_init", match_tick_rate = 1 -- the tick rate returned by match_init } Dispatcher exposes useful functions to the match. Format: { broadcast_message = function(op_code, data, presences, sender), -- numeric message op code -- a data payload string, or nil -- list of presences (a subset of match participants) to use as message targets, or nil to send to the whole match -- a presence to tag on the message as the 'sender', or nil match_kick = function(presences) -- a list of presences to remove from the match match_label_update = function(label) -- a new label to set for the match } Tick is the current match tick number, starts at 0 and increments after every match_loop call. Does not increment with calls to match_join_attempt, match_join, or match_leave. State is the current in-memory match state, may be any Lua term except nil. Grace Seconds is the number of seconds remaining until the server will shut down. Expected return these values (all required) in order: 1. An (optionally) updated state. May be any non-nil Lua term, or nil to end the match. --]] local function match_terminate(context, dispatcher, tick, state, grace_seconds) if state.debug then print("match " .. context.match_id .. " tick " .. tick) print("match " .. context.match_id .. " grace_seconds " .. grace_seconds) end return state end -- Match modules must return a table with these functions defined. All functions are required. return { match_init = match_init, match_join_attempt = match_join_attempt, match_join = match_join, match_leave = match_leave, match_loop = match_loop match_loop = match_loop, match_terminate = match_terminate } main.go +34 −3 Original line number Diff line number Diff line Loading @@ -138,14 +138,43 @@ func main() { // Wait for a termination signal. <-c startupLogger.Info("Shutting down") // Gracefully stop server components. graceSeconds := config.GetShutdownGraceSec() // If a shutdown grace period is allowed, prepare a timer. var timer *time.Timer timerCh := make(<-chan time.Time, 1) if graceSeconds != 0 { timer = time.NewTimer(time.Duration(graceSeconds) * time.Second) timerCh = timer.C startupLogger.Info("Shutdown started - use CTRL^C to force stop server", zap.Int("grace_period_sec", graceSeconds)) } else { // No grace period. startupLogger.Info("Shutdown started") } // Stop any running authoritative matches and do not accept any new ones. select { case <-matchRegistry.Stop(graceSeconds): // Graceful shutdown has completed. case <-timerCh: // Timer has expired, terminate matches immediately. startupLogger.Info("Shutdown grace period expired") <-matchRegistry.Stop(0) case <-c: // A second interrupt has been received. startupLogger.Info("Skipping graceful shutdown") <-matchRegistry.Stop(0) } if timer != nil { timer.Stop() } // Gracefully stop remaining server components. apiServer.Stop() consoleServer.Stop() metrics.Stop(logger) leaderboardScheduler.Stop() matchRegistry.Stop() tracker.Stop() sessionRegistry.Stop() Loading @@ -153,6 +182,8 @@ func main() { ga.SendSessionStop(telemetryClient, gacode, cookie) } startupLogger.Info("Shutdown complete") os.Exit(0) } Loading runtime/runtime.go +1 −0 Original line number Diff line number Diff line Loading @@ -220,6 +220,7 @@ type Match interface { MatchJoin(ctx context.Context, logger *log.Logger, db *sql.DB, nk NakamaModule, dispatcher MatchDispatcher, tick int64, state interface{}, presences []Presence) interface{} MatchLeave(ctx context.Context, logger *log.Logger, db *sql.DB, nk NakamaModule, dispatcher MatchDispatcher, tick int64, state interface{}, presences []Presence) interface{} MatchLoop(ctx context.Context, logger *log.Logger, db *sql.DB, nk NakamaModule, dispatcher MatchDispatcher, tick int64, state interface{}, messages []MatchData) interface{} MatchTerminate(ctx context.Context, logger *log.Logger, db *sql.DB, nk NakamaModule, dispatcher MatchDispatcher, tick int64, state interface{}, graceSeconds int) interface{} } type NotificationSend struct { Loading sample_go_module/sample.go +9 −0 Original line number Diff line number Diff line Loading @@ -105,3 +105,12 @@ func (m *Match) MatchLoop(ctx context.Context, logger *log.Logger, db *sql.DB, n } return state } func (m *Match) MatchTerminate(ctx context.Context, logger *log.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, graceSeconds int) interface{} { if state.(*MatchState).debug { logger.Printf("match terminate match_id %v tick %v", ctx.Value(runtime.RUNTIME_CTX_MATCH_ID), tick) logger.Printf("match terminate match_id %v grace seconds %v", ctx.Value(runtime.RUNTIME_CTX_MATCH_ID), graceSeconds) } return state } Loading
CHANGELOG.md +1 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr - Lua runtime AES-256 functions. - Lua runtime token generator function now returns a second value representing the token's expiry. - Add local cache for in-memory storage to the Lua runtime. - Graceful server shutdown and match termination. ### Changed - Improved Postgres compatibility on TIMESTAMPTZ types. Loading
data/modules/match.lua +46 −1 Original line number Diff line number Diff line Loading @@ -262,11 +262,56 @@ local function match_loop(context, dispatcher, tick, state, messages) end end --[[ Called when the server begins a graceful shutdown process. Will not be called if graceful shutdown is disabled. Context represents information about the match and server, for information purposes. Format: { env = {}, -- key-value data set in the runtime.env server configuration. executionMode = "Match", match_id = "client-friendly match ID, can be shared with clients and used in match join operations", match_node = "name of the Nakama node hosting this match", match_label = "the label string returned from match_init", match_tick_rate = 1 -- the tick rate returned by match_init } Dispatcher exposes useful functions to the match. Format: { broadcast_message = function(op_code, data, presences, sender), -- numeric message op code -- a data payload string, or nil -- list of presences (a subset of match participants) to use as message targets, or nil to send to the whole match -- a presence to tag on the message as the 'sender', or nil match_kick = function(presences) -- a list of presences to remove from the match match_label_update = function(label) -- a new label to set for the match } Tick is the current match tick number, starts at 0 and increments after every match_loop call. Does not increment with calls to match_join_attempt, match_join, or match_leave. State is the current in-memory match state, may be any Lua term except nil. Grace Seconds is the number of seconds remaining until the server will shut down. Expected return these values (all required) in order: 1. An (optionally) updated state. May be any non-nil Lua term, or nil to end the match. --]] local function match_terminate(context, dispatcher, tick, state, grace_seconds) if state.debug then print("match " .. context.match_id .. " tick " .. tick) print("match " .. context.match_id .. " grace_seconds " .. grace_seconds) end return state end -- Match modules must return a table with these functions defined. All functions are required. return { match_init = match_init, match_join_attempt = match_join_attempt, match_join = match_join, match_leave = match_leave, match_loop = match_loop match_loop = match_loop, match_terminate = match_terminate }
main.go +34 −3 Original line number Diff line number Diff line Loading @@ -138,14 +138,43 @@ func main() { // Wait for a termination signal. <-c startupLogger.Info("Shutting down") // Gracefully stop server components. graceSeconds := config.GetShutdownGraceSec() // If a shutdown grace period is allowed, prepare a timer. var timer *time.Timer timerCh := make(<-chan time.Time, 1) if graceSeconds != 0 { timer = time.NewTimer(time.Duration(graceSeconds) * time.Second) timerCh = timer.C startupLogger.Info("Shutdown started - use CTRL^C to force stop server", zap.Int("grace_period_sec", graceSeconds)) } else { // No grace period. startupLogger.Info("Shutdown started") } // Stop any running authoritative matches and do not accept any new ones. select { case <-matchRegistry.Stop(graceSeconds): // Graceful shutdown has completed. case <-timerCh: // Timer has expired, terminate matches immediately. startupLogger.Info("Shutdown grace period expired") <-matchRegistry.Stop(0) case <-c: // A second interrupt has been received. startupLogger.Info("Skipping graceful shutdown") <-matchRegistry.Stop(0) } if timer != nil { timer.Stop() } // Gracefully stop remaining server components. apiServer.Stop() consoleServer.Stop() metrics.Stop(logger) leaderboardScheduler.Stop() matchRegistry.Stop() tracker.Stop() sessionRegistry.Stop() Loading @@ -153,6 +182,8 @@ func main() { ga.SendSessionStop(telemetryClient, gacode, cookie) } startupLogger.Info("Shutdown complete") os.Exit(0) } Loading
runtime/runtime.go +1 −0 Original line number Diff line number Diff line Loading @@ -220,6 +220,7 @@ type Match interface { MatchJoin(ctx context.Context, logger *log.Logger, db *sql.DB, nk NakamaModule, dispatcher MatchDispatcher, tick int64, state interface{}, presences []Presence) interface{} MatchLeave(ctx context.Context, logger *log.Logger, db *sql.DB, nk NakamaModule, dispatcher MatchDispatcher, tick int64, state interface{}, presences []Presence) interface{} MatchLoop(ctx context.Context, logger *log.Logger, db *sql.DB, nk NakamaModule, dispatcher MatchDispatcher, tick int64, state interface{}, messages []MatchData) interface{} MatchTerminate(ctx context.Context, logger *log.Logger, db *sql.DB, nk NakamaModule, dispatcher MatchDispatcher, tick int64, state interface{}, graceSeconds int) interface{} } type NotificationSend struct { Loading
sample_go_module/sample.go +9 −0 Original line number Diff line number Diff line Loading @@ -105,3 +105,12 @@ func (m *Match) MatchLoop(ctx context.Context, logger *log.Logger, db *sql.DB, n } return state } func (m *Match) MatchTerminate(ctx context.Context, logger *log.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, graceSeconds int) interface{} { if state.(*MatchState).debug { logger.Printf("match terminate match_id %v tick %v", ctx.Value(runtime.RUNTIME_CTX_MATCH_ID), tick) logger.Printf("match terminate match_id %v grace seconds %v", ctx.Value(runtime.RUNTIME_CTX_MATCH_ID), graceSeconds) } return state }