Skip to main content

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}