Skip to content

Commit 300c6e5

Browse files
refactor(procmgr): move ProcessManager into process_manager.rs
Keeps the manager module split intuitive: process_manager.rs owns the struct and handlers, while mod.rs retains shared helpers and tests.
1 parent b1095ea commit 300c6e5

2 files changed

Lines changed: 253 additions & 237 deletions

File tree

pkg/procmgr/rust/src/manager/mod.rs

Lines changed: 9 additions & 237 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,22 @@
55

66
#![allow(clippy::result_large_err)]
77

8+
mod process_manager;
89
mod reload;
910
mod supervisor;
1011

1112
use supervisor::RuntimeHandles;
1213

13-
use crate::command::{CreateResult, StartResult, StopResult};
14-
use crate::config::{self, ConfigLoader, ProcessDefinition};
14+
use crate::config::ProcessDefinition;
1515
use crate::ordering;
1616
use crate::process::ManagedProcess;
17-
use crate::shutdown;
18-
use crate::uuid_gen::UuidGenerator;
1917
use anyhow::Result;
20-
use log::{debug, info, warn};
18+
use log::{debug, warn};
2119
use std::sync::Arc;
22-
use tokio::sync::{RwLock, mpsc};
20+
use tokio::sync::mpsc;
2321
use tonic::Status;
2422

23+
pub use process_manager::ProcessManager;
2524
pub use supervisor::Supervisor;
2625

2726
#[cfg(test)]
@@ -35,235 +34,8 @@ pub(crate) struct ExitEvent {
3534

3635
#[derive(Debug, Clone, PartialEq, Eq)]
3736
pub(crate) struct PendingRestart {
38-
uuid: String,
39-
config_generation: u64,
40-
}
41-
42-
#[derive(Clone)]
43-
pub struct ProcessManager {
44-
pub(super) processes: Arc<RwLock<Vec<ManagedProcess>>>,
45-
pub(super) startup_order: Arc<RwLock<Vec<usize>>>,
46-
pub(super) config_loader: Arc<dyn ConfigLoader>,
47-
pub(super) uuid_gen: Arc<dyn UuidGenerator>,
48-
}
49-
50-
impl ProcessManager {
51-
pub fn new(config_loader: Arc<dyn ConfigLoader>, uuid_gen: Arc<dyn UuidGenerator>) -> Self {
52-
let configs = config_loader.load();
53-
let mut processes: Vec<ManagedProcess> = configs
54-
.into_iter()
55-
.map(|pd| ManagedProcess::new_config(pd.name, uuid_gen.generate(), pd.config))
56-
.collect();
57-
for proc in &mut processes {
58-
proc.record_config_gate_met();
59-
}
60-
let startup_result = recompute_startup_order(&processes);
61-
Self {
62-
processes: Arc::new(RwLock::new(processes)),
63-
startup_order: Arc::new(RwLock::new(startup_result.order)),
64-
config_loader,
65-
uuid_gen,
66-
}
67-
}
68-
69-
/// Wrap this manager in a [`Supervisor`] for daemon execution.
70-
pub fn supervisor(self) -> Supervisor {
71-
Supervisor::new(self)
72-
}
73-
74-
async fn start_configured_processes(&self, handles: &RuntimeHandles) {
75-
let order = self.startup_order.read().await;
76-
let mut procs = self.processes.write().await;
77-
for &idx in order.iter() {
78-
let proc = &mut procs[idx];
79-
let name = proc.name().to_owned();
80-
if proc.may_auto_start()
81-
&& let Err(e) = try_spawn_and_watch(proc, handles)
82-
{
83-
warn!("[{name}] auto-start failed: {e:#}");
84-
queue_restart(proc, handles);
85-
}
86-
proc.record_config_gate_met();
87-
}
88-
}
89-
90-
pub(crate) async fn processes(&self) -> tokio::sync::RwLockReadGuard<'_, Vec<ManagedProcess>> {
91-
self.processes.read().await
92-
}
93-
94-
pub(crate) fn config_source(&self) -> &str {
95-
self.config_loader.source()
96-
}
97-
98-
pub(crate) fn config_location(&self) -> String {
99-
self.config_loader.location()
100-
}
101-
102-
async fn handle_exit(
103-
&self,
104-
event: ExitEvent,
105-
handles: &RuntimeHandles,
106-
) {
107-
let mut procs = self.processes.write().await;
108-
let Some(proc) = procs.iter_mut().find(|p| p.name() == event.name) else {
109-
warn!("exit event for unknown process '{}'", event.name);
110-
return;
111-
};
112-
113-
if proc.pid() == Some(event.pid) && proc.state().is_alive() {
114-
info!("[{}] exited with {}", proc.name(), event.status);
115-
proc.set_last_status(event.status);
116-
#[cfg(windows)]
117-
proc.ensure_windows_spawn_resources_released().await;
118-
queue_restart(proc, handles);
119-
return;
120-
}
121-
122-
let name = proc.name().to_owned();
123-
let current_pid = proc.pid();
124-
let state = proc.state();
125-
drop(procs);
126-
127-
debug!(
128-
"[{name}] ignoring stale exit event for pid {} (current pid: {current_pid:?}, state: {state})",
129-
event.pid
130-
);
131-
}
132-
133-
async fn complete_restart(
134-
&self,
135-
pending: PendingRestart,
136-
handles: &RuntimeHandles,
137-
) {
138-
let mut procs = self.processes.write().await;
139-
let Some(proc) = procs.iter_mut().find(|p| p.uuid() == pending.uuid) else {
140-
warn!("restart for unknown process '{}'", pending.uuid);
141-
return;
142-
};
143-
let name = proc.name().to_owned();
144-
if proc.is_running() {
145-
info!("[{name}] already running, skipping queued restart");
146-
return;
147-
}
148-
if pending.config_generation != proc.config_generation() {
149-
info!("[{name}] discarding stale retry after config reload");
150-
return;
151-
}
152-
if !proc.should_complete_pending_restart() {
153-
info!("[{name}] not restarting: policy or start conditions not met");
154-
proc.record_config_gate_met();
155-
return;
156-
}
157-
if let Err(e) = try_spawn_and_watch(proc, handles) {
158-
warn!("[{name}] restart failed: {e:#}");
159-
queue_restart(proc, handles);
160-
}
161-
proc.record_config_gate_met();
162-
}
163-
164-
async fn handle_create(
165-
&self,
166-
name: String,
167-
config: config::ProcessConfig,
168-
handles: &RuntimeHandles,
169-
) -> Result<CreateResult, Status> {
170-
if name.is_empty() {
171-
return Err(Status::invalid_argument("name must not be empty"));
172-
}
173-
if !name
174-
.chars()
175-
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.')
176-
{
177-
return Err(Status::invalid_argument(
178-
"name must only contain ASCII alphanumeric characters, hyphens, underscores, or dots",
179-
));
180-
}
181-
if config.command.is_empty() {
182-
return Err(Status::invalid_argument("command must not be empty"));
183-
}
184-
let uuid;
185-
{
186-
let mut procs = self.processes.write().await;
187-
if find_index_by_name(&procs, &name).is_some() {
188-
return Err(Status::already_exists(format!(
189-
"process '{name}' already exists"
190-
)));
191-
}
192-
let proc = ManagedProcess::new_runtime(name.clone(), self.uuid_gen.generate(), config);
193-
uuid = proc.uuid().to_owned();
194-
info!("[{name}] created via RPC (uuid={uuid})");
195-
procs.push(proc);
196-
let proc = procs.last_mut().unwrap();
197-
if proc.may_auto_start()
198-
&& let Err(e) = try_spawn_and_watch(proc, handles)
199-
{
200-
warn!("[{name}] auto-start failed: {e:#}");
201-
}
202-
}
203-
let warnings = self.update_startup_order().await;
204-
Ok(CreateResult { uuid, warnings })
205-
}
206-
207-
async fn handle_start(
208-
&self,
209-
name_or_uuid: &str,
210-
handles: &RuntimeHandles,
211-
) -> Result<StartResult, Status> {
212-
let mut procs = self.processes.write().await;
213-
let idx = resolve_index(&procs, name_or_uuid)?;
214-
let proc = &mut procs[idx];
215-
let name = proc.name().to_owned();
216-
217-
if proc.is_running() {
218-
return Err(Status::failed_precondition(format!(
219-
"process '{name}' is already running",
220-
)));
221-
}
222-
try_spawn_and_watch(proc, handles)
223-
.map_err(|e| Status::internal(format!("failed to start '{name}': {e:#}")))?;
224-
proc.record_config_gate_met();
225-
Ok(StartResult {
226-
uuid: proc.uuid().to_owned(),
227-
pid: proc.pid(),
228-
state: proc.state(),
229-
})
230-
}
231-
232-
async fn handle_stop(&self, name_or_uuid: &str) -> Result<StopResult, Status> {
233-
let mut procs = self.processes.write().await;
234-
let idx = resolve_index(&procs, name_or_uuid)?;
235-
let proc = &mut procs[idx];
236-
237-
if !proc.is_running() {
238-
return Err(Status::failed_precondition(format!(
239-
"process '{}' is not running",
240-
proc.name()
241-
)));
242-
}
243-
let uuid = proc.uuid().to_owned();
244-
proc.stop().await;
245-
let state = proc.state();
246-
Ok(StopResult { uuid, state })
247-
}
248-
249-
async fn update_startup_order(&self) -> Vec<String> {
250-
let result = recompute_startup_order(&self.processes.read().await);
251-
*self.startup_order.write().await = result.order;
252-
result.warnings
253-
}
254-
255-
async fn shutdown(&self) {
256-
let order: Vec<usize> = self
257-
.startup_order
258-
.read()
259-
.await
260-
.iter()
261-
.copied()
262-
.rev()
263-
.collect();
264-
let mut procs = self.processes.write().await;
265-
shutdown::shutdown_ordered(&mut procs, &order).await;
266-
}
37+
pub(super) uuid: String,
38+
pub(super) config_generation: u64,
26739
}
26840

26941
pub fn looks_like_uuid_prefix(s: &str) -> bool {
@@ -382,10 +154,10 @@ fn recompute_startup_order(procs: &[ManagedProcess]) -> StartupOrderResult {
382154
#[cfg(test)]
383155
mod tests {
384156
use super::*;
385-
use crate::config::{MutableConfigLoader, ProcessConfig, RestartPolicy, StaticConfigLoader};
157+
use crate::config::{ConfigLoader, MutableConfigLoader, ProcessConfig, RestartPolicy, StaticConfigLoader};
386158
use crate::state::ProcessState;
387159
use crate::test_helpers;
388-
use crate::uuid_gen::{SequentialUuidGenerator, V4UuidGenerator};
160+
use crate::uuid_gen::{SequentialUuidGenerator, UuidGenerator, V4UuidGenerator};
389161

390162
fn loader(defs: Vec<ProcessDefinition>) -> Arc<dyn ConfigLoader> {
391163
Arc::new(StaticConfigLoader::new(defs))

0 commit comments

Comments
 (0)