Module: Lich::InternalAPI::ActiveSessions::Lifecycle Private
- Defined in:
- documented/internal_api/active_sessions/lifecycle.rb
Overview
This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.
Process-local lifecycle coordinator for active sessions registration.
This module adapts Lich runtime state into active-sessions payloads. It intentionally keeps only a small amount of mutable process-local state: identifying metadata, connection state, detachable listener details, and a heartbeat thread handle.
ActiveSessions API contract:
- A session record being present in the registry means the Lich process is still known to the active-sessions service. Presence is not a promise that the game connection is still usable.
- The
connectedfield is the authoritative connection signal exposed to API consumers. Shutdown code must set it tofalsewhen the game connection has ended, before slower teardown work such as script before_dying hooks, state persistence, socket closeout, and database closeout runs. - Lifecycle
stopunregisters the process from ActiveSessions. That removal means the process is no longer reporting as an active session; it should not be used merely to mean "the game connection dropped." - For detachable sessions, the public
connectedvalue is true only when both the game connection and detachable listener connection are active. A disconnected game session stays disconnected even if the detachable listener was last reported as connected.
Constant Summary collapse
- HEARTBEAT_INTERVAL_SECONDS =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Default heartbeat cadence for refreshing the current process entry and detecting service-owner failover quickly enough for multi-session use.
2
Class Method Summary collapse
-
.clear_listener ⇒ void
private
Clears detachable listener metadata for the current session.
-
.current_payload ⇒ Hash
private
Returns the current process session payload.
-
.resolve_role(argv:, detachable_client_port:) ⇒ String
private
Resolves the logical runtime role for active sessions reporting.
-
.resolve_session_name(argv:, account_character: nil) ⇒ String
private
Resolves the reporting session name from runtime context.
-
.start(session_name:, role:, heartbeat_interval: HEARTBEAT_INTERVAL_SECONDS) ⇒ Boolean
private
Starts lifecycle registration and periodic heartbeats.
-
.stop ⇒ Boolean
private
Stops lifecycle registration and removes the current process session.
-
.update_connected(connected) ⇒ void
private
Updates the current process connection state without unregistering the session from the active-sessions registry.
-
.update_listener(host:, port:, connected:) ⇒ void
private
Updates detachable listener metadata for the current session.
Class Method Details
.clear_listener ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Clears detachable listener metadata for the current session.
This is used when detachable listener infrastructure is torn down and the public snapshot should stop reporting a listener endpoint.
224 225 226 227 228 229 230 231 232 233 |
# File 'documented/internal_api/active_sessions/lifecycle.rb', line 224 def self.clear_listener return unless started? @mutex.synchronize do @listener_host = nil @listener_port = nil @listener_connected = false end upsert_current_session end |
.current_payload ⇒ Hash
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the current process session payload.
274 275 276 |
# File 'documented/internal_api/active_sessions/lifecycle.rb', line 274 def self.current_payload @mutex.synchronize { build_current_payload } end |
.resolve_role(argv:, detachable_client_port:) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Resolves the logical runtime role for active sessions reporting.
88 89 90 91 92 93 |
# File 'documented/internal_api/active_sessions/lifecycle.rb', line 88 def self.resolve_role(argv:, detachable_client_port:) return 'headless' if argv.include?('--without-frontend') return 'detachable' unless detachable_client_port.nil? 'session' end |
.resolve_session_name(argv:, account_character: nil) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Resolves the reporting session name from runtime context.
71 72 73 74 75 76 77 78 79 80 81 |
# File 'documented/internal_api/active_sessions/lifecycle.rb', line 71 def self.resolve_session_name(argv:, account_character: nil) if (login_idx = argv.index('--login')) && argv[login_idx + 1] argv[login_idx + 1].capitalize elsif account_character && !account_character.to_s.empty? account_character elsif defined?(XMLData) && XMLData.respond_to?(:name) && !XMLData.name.to_s.empty? XMLData.name else "pid-#{Process.pid}" end end |
.start(session_name:, role:, heartbeat_interval: HEARTBEAT_INTERVAL_SECONDS) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Starts lifecycle registration and periodic heartbeats.
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'documented/internal_api/active_sessions/lifecycle.rb', line 101 def self.start(session_name:, role:, heartbeat_interval: HEARTBEAT_INTERVAL_SECONDS) feature_enabled = ActiveSessions.enabled? return false unless feature_enabled # Bootstrap once during lifecycle startup so the admitted-only # heartbeat/update path has a running service to talk to. ActiveSessions.ensure_service! thread = nil @mutex.synchronize do return false if @started @session_name = session_name @role = role @started_at = Time.now.to_i @connected = true @feature_enabled = feature_enabled @running = true @started = true @lifecycle_generation += 1 end thread = Thread.new do loop do sleep heartbeat_interval break unless running? begin upsert_current_session rescue StandardError => e Lich.log("warning: ActiveSessions heartbeat tick failed (continuing): #{e.class}: #{e.}\n\t#{e.backtrace&.first(3)&.join("\n\t")}") if Lich.respond_to?(:log) end end end @mutex.synchronize { @heartbeat_thread = thread if @started } upsert_current_session true rescue StandardError => e @mutex.synchronize do @running = false @started = false @heartbeat_thread = nil @session_name = nil @role = nil @listener_host = nil @listener_port = nil @listener_connected = false @connected = true @started_at = nil @feature_enabled = false end thread.kill if thread.respond_to?(:alive?) && thread.alive? Lich.log("warning: ActiveSessions lifecycle start failed: #{e.class}: #{e.}") if Lich.respond_to?(:log) false end |
.stop ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Stops lifecycle registration and removes the current process session.
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'documented/internal_api/active_sessions/lifecycle.rb', line 162 def self.stop thread = nil lifecycle_active = false @mutex.synchronize do lifecycle_active = @started || !@heartbeat_thread.nil? || @running return false unless lifecycle_active @running = false @started = false @lifecycle_generation += 1 thread = @heartbeat_thread @heartbeat_thread = nil end thread&.join(0.5) thread&.kill if thread&.alive? if feature_enabled? @registration_mutex.synchronize do ActiveSessions.send(:unregister_session_admitted, pid: Process.pid) ActiveSessions.cleanup_discovery_if_last_session! end end @mutex.synchronize do @session_name = nil @role = nil @listener_host = nil @listener_port = nil @listener_connected = false @connected = true @started_at = nil @feature_enabled = false end true rescue StandardError => e Lich.log("warning: ActiveSessions lifecycle stop failed: #{e.class}: #{e.}") if Lich.respond_to?(:log) false end |
.update_connected(connected) ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Updates the current process connection state without unregistering the session from the active-sessions registry.
This method exists because MahtraDR's shutdown testing demonstrated
that immediate unregister is too blunt an API signal: it hides a
still-running Lich process from ActiveSessions while scripts, saves,
socket closeout, and database closeout may still be executing. The
narrower contract is to keep the session present while publishing
connected: false.
API contract for callers:
update_connected(false)means the game/session connection is no longer available for normal use, but the Lich process may still be performing shutdown work.update_connected(true)may restore the connection state for a started lifecycle without changing identity, uptime, listener metadata, or registration ownership.- The method is a no-op before
start; it must not create a registry record or bootstrap ActiveSessions on its own. - The method updates the existing registry record through the same admitted lifecycle path used by heartbeats and listener updates; it must not unregister the session.
- The published
connectedvalue is combined with detachable listener state, so a detachable session reports connected only when both the game connection and listener connection are active.
264 265 266 267 268 269 |
# File 'documented/internal_api/active_sessions/lifecycle.rb', line 264 def self.update_connected(connected) return unless started? @mutex.synchronize { @connected = !!connected } upsert_current_session end |
.update_listener(host:, port:, connected:) ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Updates detachable listener metadata for the current session.
207 208 209 210 211 212 213 214 215 216 |
# File 'documented/internal_api/active_sessions/lifecycle.rb', line 207 def self.update_listener(host:, port:, connected:) return unless started? @mutex.synchronize do @listener_host = host @listener_port = port @listener_connected = connected end upsert_current_session end |