Module: Lich::Common::ShutdownCoordinator

Defined in:
documented/common/shutdown_coordinator.rb

Overview

Records process shutdown attribution and coarse shutdown progress.

The coordinator is deliberately state-only. It records first-wins shutdown intent and stores the result of any higher-level shutdown runner, but it does not perform teardown work itself.

Defined Under Namespace

Classes: Request

Constant Summary collapse

ALLOWED_REASONS =

Shutdown reasons accepted by request.

[
  :user_exit,
  :client_disconnect,
  :game_eof,
  :game_timeout,
  :connection_reset,
  :connection_pipe,
  :connection_aborted,
  :game_stream_desync,
  :unrecoverable_game_thread_error,
].freeze
CONNECTION_LOSS_REASONS =

Reasons that indicate local frontend loss or remote game connection loss.

[
  :client_disconnect,
  :game_eof,
  :game_timeout,
  :connection_reset,
  :connection_pipe,
  :connection_aborted,
  :game_stream_desync,
  :unrecoverable_game_thread_error,
].freeze

Class Method Summary collapse

Class Method Details

.begin_best_effort_cleanup(result) ⇒ Object

Stores the in-progress or completed best-effort cleanup result once.

Parameters:

  • result (#completed?)

    result object produced by a cleanup runner

Returns:

  • (Object)

    stored result; may be a previously stored result

Raises:

  • (ArgumentError)

    when result is nil or missing expected predicates



114
115
116
117
118
119
120
# File 'documented/common/shutdown_coordinator.rb', line 114

def begin_best_effort_cleanup(result)
  validate_cleanup_result!(result)

  mutex.synchronize do
    @best_effort_cleanup_result ||= result
  end
end

.begin_orderly_shutdown(result) ⇒ Object

Stores the in-progress or completed orderly-shutdown result once.

Parameters:

  • result (#completed?)

    result object produced by the orderly runner

Returns:

  • (Object)

    stored result; may be a previously stored result

Raises:

  • (ArgumentError)

    when result is nil or missing expected predicates



101
102
103
104
105
106
107
# File 'documented/common/shutdown_coordinator.rb', line 101

def begin_orderly_shutdown(result)
  validate_orderly_shutdown_result!(result)

  mutex.synchronize do
    @orderly_shutdown_result ||= result
  end
end

.best_effort_cleanup_completed?Boolean

Returns whether best-effort cleanup completed all local steps.

Returns:

  • (Boolean)

    whether best-effort cleanup completed all local steps



138
139
140
# File 'documented/common/shutdown_coordinator.rb', line 138

def best_effort_cleanup_completed?
  best_effort_cleanup_result&.completed?
end

.best_effort_cleanup_resultObject?

Returns best-effort cleanup result if cleanup started.

Returns:

  • (Object, nil)

    best-effort cleanup result if cleanup started



128
129
130
# File 'documented/common/shutdown_coordinator.rb', line 128

def best_effort_cleanup_result
  mutex.synchronize { @best_effort_cleanup_result }
end

.client_socket_write_failed?Boolean

Returns whether the client socket write path failed fatally.

Returns:

  • (Boolean)

    whether the client socket write path failed fatally



190
191
192
# File 'documented/common/shutdown_coordinator.rb', line 190

def client_socket_write_failed?
  !client_socket_write_failure.nil?
end

.client_socket_write_failureException?

Returns first fatal client socket write failure.

Returns:

  • (Exception, nil)

    first fatal client socket write failure



185
186
187
# File 'documented/common/shutdown_coordinator.rb', line 185

def client_socket_write_failure
  mutex.synchronize { @client_socket_write_failure }
end

.connection_loss?Boolean

Returns whether shutdown began from local or remote connection loss.

Returns:

  • (Boolean)

    whether shutdown began from local or remote connection loss



92
93
94
# File 'documented/common/shutdown_coordinator.rb', line 92

def connection_loss?
  CONNECTION_LOSS_REASONS.include?(reason)
end

.currentRequest?

Returns stored first shutdown request.

Returns:

  • (Request, nil)

    stored first shutdown request



77
78
79
# File 'documented/common/shutdown_coordinator.rb', line 77

def current
  mutex.synchronize { @request }
end

.orderly_shutdown_completed?Boolean

Returns whether the orderly-shutdown runner completed all steps.

Returns:

  • (Boolean)

    whether the orderly-shutdown runner completed all steps



133
134
135
# File 'documented/common/shutdown_coordinator.rb', line 133

def orderly_shutdown_completed?
  orderly_shutdown_result&.completed?
end

.orderly_shutdown_resultObject?

Returns orderly-shutdown result if a user-exit runner started.

Returns:

  • (Object, nil)

    orderly-shutdown result if a user-exit runner started



123
124
125
# File 'documented/common/shutdown_coordinator.rb', line 123

def orderly_shutdown_result
  mutex.synchronize { @orderly_shutdown_result }
end

.orderly_user_exit?Boolean

Returns whether shutdown was explicitly requested by user input.

Returns:

  • (Boolean)

    whether shutdown was explicitly requested by user input



87
88
89
# File 'documented/common/shutdown_coordinator.rb', line 87

def orderly_user_exit?
  reason == :user_exit
end

.reasonSymbol?

Returns stored shutdown reason.

Returns:

  • (Symbol, nil)

    stored shutdown reason



82
83
84
# File 'documented/common/shutdown_coordinator.rb', line 82

def reason
  current&.reason
end

.record_client_socket_write_failure(error:, source: :client_socket_write) ⇒ Exception

Records fatal client socket write failure context once.

If no shutdown request exists yet, the write failure is treated as a client disconnect. If another shutdown reason already exists, first-wins attribution is preserved while the transport failure remains visible.

Parameters:

  • error (Exception)

    fatal socket write error

  • source (#to_s) (defaults to: :client_socket_write)

    subsystem that observed the failed write

Returns:

  • (Exception)

    stored first client socket write failure

Raises:

  • (ArgumentError)


161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'documented/common/shutdown_coordinator.rb', line 161

def record_client_socket_write_failure(error:, source: :client_socket_write)
  raise ArgumentError, "client socket write failure error must be present" if error.nil?

  validate_source!(source)

  stored_error = nil
  detail = nil
  request_needed = false
  mutex.synchronize do
    stored_error = (@client_socket_write_failure ||= error)
    detail = "#{stored_error.class}: #{stored_error.message}"
    request_needed = @request.nil?
  end

  request(
    reason: :client_disconnect,
    source: source,
    detail: detail
  ) if request_needed

  stored_error
end

.request(reason:, source:, detail: nil) ⇒ Request

Records shutdown intent once.

First request wins so later fallout, such as Game.close unblocking the reader thread, does not overwrite the initiating reason.

Parameters:

  • reason (Symbol)
  • source (#to_s)

    subsystem that observed the request

  • detail (Object, nil) (defaults to: nil)

    optional compact diagnostic detail

Returns:

  • (Request)

    the stored first request

Raises:

  • (ArgumentError)

    when reason/source is invalid



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'documented/common/shutdown_coordinator.rb', line 54

def request(reason:, source:, detail: nil)
  validate_reason!(reason)
  validate_source!(source)

  mutex.synchronize do
    @request ||= Request.new(
      reason: reason,
      source: source.to_s,
      detail: detail,
      requested_at: Time.now
    ).tap do |shutdown_request|
      log_request(shutdown_request)
      shutdown_request.freeze
    end
  end
end

.requested?Boolean

Returns whether any shutdown request has been recorded.

Returns:

  • (Boolean)

    whether any shutdown request has been recorded



72
73
74
# File 'documented/common/shutdown_coordinator.rb', line 72

def requested?
  !current.nil?
end

.reset!nil

Clears coordinator state for tests and process reinitialization.

Returns:

  • (nil)


197
198
199
200
201
202
203
204
# File 'documented/common/shutdown_coordinator.rb', line 197

def reset!
  mutex.synchronize do
    @request = nil
    @orderly_shutdown_result = nil
    @best_effort_cleanup_result = nil
    @client_socket_write_failure = nil
  end
end

.scripts_drained?Boolean

Returns whether orderly shutdown or best-effort cleanup fully drained scripts.

Returns:

  • (Boolean)

    whether orderly shutdown or best-effort cleanup fully drained scripts



143
144
145
# File 'documented/common/shutdown_coordinator.rb', line 143

def scripts_drained?
  orderly_shutdown_result&.scripts_drained? || best_effort_cleanup_result&.scripts_drained?
end

.vars_saved?Boolean

Returns whether orderly shutdown or best-effort cleanup saved script settings.

Returns:

  • (Boolean)

    whether orderly shutdown or best-effort cleanup saved script settings



148
149
150
# File 'documented/common/shutdown_coordinator.rb', line 148

def vars_saved?
  orderly_shutdown_result&.vars_saved? || best_effort_cleanup_result&.vars_saved?
end