Class: MCPClient::ServerStdio::ChildSession
- Inherits:
-
Object
- Object
- MCPClient::ServerStdio::ChildSession
- Defined in:
- lib/mcp_client/server_stdio/child_session.rb
Overview
The record of one server child process: when the open subscriptions were handed to it, and when its handles were torn down. One record per process, written only by that process's own lifecycle, so nothing another thread does to another process can change what this one says.
It exists for the crash-loop bound. That bound used to live in flags and a timestamp on the transport, which two restarts (or a restart and a host request that re-established the process first) raced over: whichever finished last decided what the other process's uptime had been, and a server that exited on sight could be respawned for ever. The question a restart actually has to answer is about one particular process — "did the last process we gave these subscriptions to die straight after we gave them to it?" — and both facts it needs are recorded here, on that process, at the two moments they happen.
Instance Attribute Summary collapse
-
#carried_at ⇒ Float?
readonly
Monotonic time the open subscriptions were re-sent to this process, nil if it was never asked to carry any.
-
#ended_at ⇒ Float?
readonly
Monotonic time this process's handles were torn down, nil while it is still the live session.
Class Method Summary collapse
-
.now ⇒ Float
Monotonic seconds.
Instance Method Summary collapse
-
#carrying_subscriptions ⇒ void
This process has been handed the subscriptions a previous process left open.
-
#died_carrying_subscriptions?(min_uptime) ⇒ Boolean
Whether this process died too soon after being given the subscriptions to be given them again — the crash loop the restart bound exists to stop.
-
#ended ⇒ void
This process is gone (its stdio handles have been torn down).
-
#exited_unexpectedly ⇒ void
This process exited on its own — the reader saw EOF on a stdin the client had not closed (see #handle_server_exit).
-
#exited_unexpectedly? ⇒ Boolean
Whether this process went on its own rather than being torn down by the client.
-
#initialize ⇒ ChildSession
constructor
A new instance of ChildSession.
Constructor Details
#initialize ⇒ ChildSession
Returns a new instance of ChildSession.
27 28 29 30 31 |
# File 'lib/mcp_client/server_stdio/child_session.rb', line 27 def initialize @carried_at = nil @ended_at = nil @exited_unexpectedly = false end |
Instance Attribute Details
#carried_at ⇒ Float? (readonly)
Returns monotonic time the open subscriptions were re-sent to this process, nil if it was never asked to carry any.
22 23 24 |
# File 'lib/mcp_client/server_stdio/child_session.rb', line 22 def carried_at @carried_at end |
#ended_at ⇒ Float? (readonly)
Returns monotonic time this process's handles were torn down, nil while it is still the live session.
25 26 27 |
# File 'lib/mcp_client/server_stdio/child_session.rb', line 25 def ended_at @ended_at end |
Class Method Details
.now ⇒ Float
Returns monotonic seconds.
40 41 42 |
# File 'lib/mcp_client/server_stdio/child_session.rb', line 40 def self.now Process.clock_gettime(Process::CLOCK_MONOTONIC) end |
Instance Method Details
#carrying_subscriptions ⇒ void
This method returns an undefined value.
This process has been handed the subscriptions a previous process left open. Stamped before they go out, not after: the process can exit while they are still being written, and an exit under the re-send is the clearest crash loop there is.
49 50 51 |
# File 'lib/mcp_client/server_stdio/child_session.rb', line 49 def @carried_at = ChildSession.now end |
#died_carrying_subscriptions?(min_uptime) ⇒ Boolean
Whether this process died too soon after being given the subscriptions to be given them again — the crash loop the restart bound exists to stop. A process that was never given any is not part of that loop, and one that is still alive has not ended anything.
Neither is a process the client itself shut down. A host that closes
the transport and reconnects — as a cleanup/request cycle does, and
as re-authenticating or re-configuring a server does — tears the
process down whenever it likes, and reading that as the server crashing
closed the very subscriptions the reconnect exists to carry across.
Only an exit the reader actually watched happen counts.
The interval is measured from the moment it received them, so a server that is slow to start is credited with none of its own handshake, and a process that was already gone when they were sent to it (its exit stamped before the re-send) counts as having survived no time at all.
91 92 93 94 95 |
# File 'lib/mcp_client/server_stdio/child_session.rb', line 91 def (min_uptime) return false unless @exited_unexpectedly && @carried_at && @ended_at (@ended_at - @carried_at) < min_uptime end |
#ended ⇒ void
This method returns an undefined value.
This process is gone (its stdio handles have been torn down). First
stamp wins: a host cleanup racing the reader's own teardown must not
move the moment the process ended.
57 58 59 60 61 |
# File 'lib/mcp_client/server_stdio/child_session.rb', line 57 def ended return if @ended_at @ended_at = ChildSession.now end |
#exited_unexpectedly ⇒ void
This method returns an undefined value.
This process exited on its own — the reader saw EOF on a stdin the client had not closed (see MCPClient::ServerStdio#handle_server_exit). Recorded separately from #ended, which every teardown stamps, because only an exit is a crash.
68 69 70 |
# File 'lib/mcp_client/server_stdio/child_session.rb', line 68 def exited_unexpectedly @exited_unexpectedly = true end |
#exited_unexpectedly? ⇒ Boolean
Returns whether this process went on its own rather than being torn down by the client.
35 36 37 |
# File 'lib/mcp_client/server_stdio/child_session.rb', line 35 def exited_unexpectedly? @exited_unexpectedly end |