1use std::{
2 future::Future,
3 sync::{Arc, Mutex},
4 time::SystemTime,
5};
6
7use futures::future::BoxFuture;
8use tokio::sync::{mpsc, oneshot, watch};
9
10use crate::{Dependency, Error, PluginContext, Result};
11
12pub trait Plugin: Send + Sync + 'static {
15 type Config: Send + Sync + 'static;
16
17 fn name(&self) -> &'static str;
18
19 fn dependencies(&self) -> Vec<Dependency> {
20 Vec::new()
21 }
22
23 fn provides(&self) -> Vec<crate::ServiceDeclaration> {
24 Vec::new()
25 }
26
27 fn apply(
28 &self,
29 ctx: PluginContext,
30 config: Arc<Self::Config>,
31 ) -> impl Future<Output = Result<()>> + Send;
32}
33
34pub type ErasedConfig = Arc<dyn std::any::Any + Send + Sync>;
35
36pub trait ErasedPlugin: Send + Sync + 'static {
39 fn name(&self) -> &'static str;
40
41 fn dependencies(&self) -> Vec<Dependency> {
42 Vec::new()
43 }
44
45 fn provides(&self) -> Vec<crate::ServiceDeclaration> {
46 Vec::new()
47 }
48
49 fn apply(&self, ctx: PluginContext, config: ErasedConfig) -> BoxFuture<'static, Result<()>>;
50}
51
52#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
53pub struct PluginId(pub(crate) u64);
54
55#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
56pub struct ActivationId(pub(crate) u64);
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub enum FailurePhase {
60 Apply,
61 Dispose,
62}
63
64#[derive(Clone, Debug, PartialEq, Eq)]
65#[non_exhaustive]
66pub enum PluginStatus {
67 Suspended {
68 missing: Arc<[&'static str]>,
69 },
70 Starting {
71 revision: u64,
72 },
73 Active {
74 activation: ActivationId,
75 revision: u64,
76 },
77 Stopping {
78 activation: ActivationId,
79 },
80 Failed {
81 phase: FailurePhase,
82 message: Arc<str>,
83 revision: u64,
84 },
85 Disposed,
86}
87
88#[derive(Clone, Debug)]
89pub struct PluginDiagnostic {
90 pub at: SystemTime,
91 pub status: PluginStatus,
92}
93
94pub(crate) enum PluginCommand {
95 Reload(oneshot::Sender<Result<()>>),
96 Retry(oneshot::Sender<Result<()>>),
97 Quiesce(oneshot::Sender<Result<()>>),
98 Resume(oneshot::Sender<Result<()>>),
99 Dispose(oneshot::Sender<Result<()>>),
100}
101
102#[must_use = "keep the handle to inspect status or call dispose().await"]
105#[derive(Clone)]
106pub struct PluginHandle {
107 id: PluginId,
108 name: &'static str,
109 commands: mpsc::Sender<PluginCommand>,
110 status: watch::Receiver<PluginStatus>,
111 diagnostics: Arc<Mutex<Vec<PluginDiagnostic>>>,
112 control: Arc<crate::app::ControlPlane>,
113 dispose_lock: Arc<tokio::sync::Mutex<()>>,
114 dispose_result: Arc<Mutex<Option<Option<Arc<str>>>>>,
115}
116
117impl PluginHandle {
118 pub(crate) fn new(
119 id: PluginId,
120 name: &'static str,
121 commands: mpsc::Sender<PluginCommand>,
122 status: watch::Receiver<PluginStatus>,
123 diagnostics: Arc<Mutex<Vec<PluginDiagnostic>>>,
124 control: Arc<crate::app::ControlPlane>,
125 ) -> Self {
126 Self {
127 id,
128 name,
129 commands,
130 status,
131 diagnostics,
132 control,
133 dispose_lock: Arc::new(tokio::sync::Mutex::new(())),
134 dispose_result: Arc::new(Mutex::new(None)),
135 }
136 }
137
138 pub fn id(&self) -> PluginId {
139 self.id
140 }
141
142 pub fn name(&self) -> &'static str {
143 self.name
144 }
145
146 pub fn status(&self) -> PluginStatus {
147 self.status.borrow().clone()
148 }
149
150 pub fn subscribe(&self) -> watch::Receiver<PluginStatus> {
151 self.status.clone()
152 }
153
154 pub fn diagnostics(&self) -> Vec<PluginDiagnostic> {
155 self.diagnostics
156 .lock()
157 .expect("diagnostics lock poisoned")
158 .clone()
159 }
160
161 pub async fn wait_active(&self) -> Result<ActivationId> {
162 let mut status = self.status.clone();
163 loop {
164 let current = status.borrow().clone();
165 match current {
166 PluginStatus::Active { activation, .. } => return Ok(activation),
167 PluginStatus::Failed { message, .. } => {
168 return Err(Error::PluginFailed(message.to_string()));
169 }
170 PluginStatus::Disposed => return Err(Error::PluginDisposed),
171 _ => {}
172 }
173 status.changed().await.map_err(|_| Error::PluginDisposed)?;
174 }
175 }
176
177 pub async fn reload(&self) -> Result<()> {
178 self.control.reload(self.id).await
179 }
180
181 pub async fn retry(&self) -> Result<()> {
182 self.request(PluginCommand::Retry).await
183 }
184
185 async fn request(
186 &self,
187 make: impl FnOnce(oneshot::Sender<Result<()>>) -> PluginCommand,
188 ) -> Result<()> {
189 let (tx, rx) = oneshot::channel();
190 self.commands
191 .send(make(tx))
192 .await
193 .map_err(|_| Error::PluginDisposed)?;
194 rx.await.map_err(|_| Error::PluginDisposed)?
195 }
196
197 pub async fn dispose(&self) -> Result<()> {
198 let _guard = self.dispose_lock.lock().await;
199 if let Some(result) = self
200 .dispose_result
201 .lock()
202 .expect("dispose result lock poisoned")
203 .clone()
204 {
205 return result.map_or(Ok(()), |message| Err(Error::cleanup(message)));
206 }
207
208 if matches!(self.status(), PluginStatus::Disposed) {
209 *self
210 .dispose_result
211 .lock()
212 .expect("dispose result lock poisoned") = Some(None);
213 return Ok(());
214 }
215
216 let result = self.control.dispose(self.id).await;
217 *self
218 .dispose_result
219 .lock()
220 .expect("dispose result lock poisoned") = Some(
221 result
222 .as_ref()
223 .err()
224 .map(|error| Arc::<str>::from(error.to_string())),
225 );
226 result
227 }
228}
229
230pub type PluginScope = PluginHandle;