55
66#![ allow( clippy:: result_large_err) ]
77
8+ mod process_manager;
89mod reload;
910mod supervisor;
1011
1112use supervisor:: RuntimeHandles ;
1213
13- use crate :: command:: { CreateResult , StartResult , StopResult } ;
14- use crate :: config:: { self , ConfigLoader , ProcessDefinition } ;
14+ use crate :: config:: ProcessDefinition ;
1515use crate :: ordering;
1616use crate :: process:: ManagedProcess ;
17- use crate :: shutdown;
18- use crate :: uuid_gen:: UuidGenerator ;
1917use anyhow:: Result ;
20- use log:: { debug, info , warn} ;
18+ use log:: { debug, warn} ;
2119use std:: sync:: Arc ;
22- use tokio:: sync:: { RwLock , mpsc} ;
20+ use tokio:: sync:: mpsc;
2321use tonic:: Status ;
2422
23+ pub use process_manager:: ProcessManager ;
2524pub use supervisor:: Supervisor ;
2625
2726#[ cfg( test) ]
@@ -35,235 +34,8 @@ pub(crate) struct ExitEvent {
3534
3635#[ derive( Debug , Clone , PartialEq , Eq ) ]
3736pub ( 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
26941pub fn looks_like_uuid_prefix ( s : & str ) -> bool {
@@ -382,10 +154,10 @@ fn recompute_startup_order(procs: &[ManagedProcess]) -> StartupOrderResult {
382154#[ cfg( test) ]
383155mod 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