onlyne_client/backend/external.rs
1//! External placement: the runtime is already resident, so the client starts
2//! nothing of its own.
3//!
4//! `plugin × external` is the row of the plan's table where the runtime stays
5//! up on its own and its plugin dials the client (`docs/v2-PLAN.md` line 289):
6//! one DSH serving several roles, each role's client single-purpose. So a
7//! session this placement opens is a name the client stages, not a process it
8//! owns: the socket the client serves is published, the plugin mounts on it,
9//! and from there the session rides the ordinary adapter path — the `assign`
10//! frame, the reports, the settle — which is why this backend writes no spawn.
11//!
12//! Two consequences are deliberate rather than missing:
13//!
14//! * [`SessionBackend::spawn`] starts nothing, whatever argv the role's
15//! `[client.runtime] command` carries. The placement is the machine's answer
16//! to "who starts the runtime", and here the answer is "the operator did".
17//! * [`SessionBackend::close`] stops nothing. The process belongs to the
18//! operator, and only this client's record of the session ends here.
19//!
20//! The death window of such a session is the adapter transport's, exactly as it
21//! is for a `plugin × orca` session: a plugin that never mounts is retired by
22//! `[client] reconnect_grace_secs`, not by a `try_wait` this backend cannot run.
23
24use super::*;
25
26#[derive(Clone, Default)]
27pub struct ExternalBackend;
28
29impl ExternalBackend {
30 pub fn new() -> Self {
31 Self
32 }
33}
34
35impl SessionBackend for ExternalBackend {
36 fn name(&self) -> &'static str {
37 "external"
38 }
39 fn capabilities(&self) -> Capabilities {
40 Capabilities {
41 spawn: true,
42 attach: true,
43 probe: true,
44 close: true,
45 focus: false,
46 rename: false,
47 }
48 }
49 fn available(&self) -> Result<bool> {
50 Ok(true)
51 }
52 fn spawn(&self, spec: SpawnSpec) -> Result<SessionRef> {
53 tracing::info!(
54 task = %spec.task_id,
55 command = %spec.command.join(" "),
56 "external placement: the client starts no process; the resident runtime dials in"
57 );
58 Ok(SessionRef {
59 task_id: spec.task_id.clone(),
60 backend: self.name().into(),
61 backend_ref: serde_json::json!({
62 "id": spec.task_id,
63 "placement": "external",
64 }),
65 generation: 1,
66 })
67 }
68 fn attach(&self, session: &SessionRef) -> Result<SessionRef> {
69 // The resource is the connection the runtime opened, and this client
70 // holds that itself: there is nothing outside the process to probe.
71 Ok(session.clone())
72 }
73 fn probe(&self, session: &SessionRef) -> Result<ResourceProbe> {
74 Ok(ResourceProbe {
75 alive: true,
76 attached: true,
77 detail: Some(serde_json::json!({
78 "placement": "external",
79 "task": session.task_id,
80 })),
81 })
82 }
83 fn close(&self, session: &SessionRef, reason: CloseReason, _force: bool) -> Result<()> {
84 tracing::info!(
85 task = %session.task_id,
86 ?reason,
87 "external session closed; no process of this client's own to stop"
88 );
89 Ok(())
90 }
91}