Skip to content

Commit 45c472e

Browse files
feat(procmgr): reject RPCs during reload and shutdown
Add an OperationGate so config reload and daemon shutdown block list, describe, get_status, get_config, create, start, and stop with FAILED_PRECONDITION while in progress.
1 parent 7bdb99b commit 45c472e

6 files changed

Lines changed: 127 additions & 1 deletion

File tree

pkg/procmgr/rust/src/grpc/service.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ impl ProcessManagerService {
3131
cmd_tx,
3232
}
3333
}
34+
35+
async fn ensure_idle(&self) -> Result<(), Status> {
36+
self.mgr.ensure_idle().await
37+
}
3438
}
3539

3640
#[tonic::async_trait]
@@ -39,6 +43,7 @@ impl proto::process_manager_server::ProcessManager for ProcessManagerService {
3943
&self,
4044
_request: Request<proto::ListRequest>,
4145
) -> Result<Response<proto::ListResponse>, Status> {
46+
self.ensure_idle().await?;
4247
let procs = self.mgr.processes().await;
4348
let processes = procs.iter().map(process_to_proto).collect();
4449
Ok(Response::new(proto::ListResponse { processes }))
@@ -48,6 +53,7 @@ impl proto::process_manager_server::ProcessManager for ProcessManagerService {
4853
&self,
4954
request: Request<proto::DescribeRequest>,
5055
) -> Result<Response<proto::DescribeResponse>, Status> {
56+
self.ensure_idle().await?;
5157
let name_or_uuid = request.into_inner().name_or_uuid;
5258
let (mut detail, pid) = {
5359
let procs = self.mgr.processes().await;
@@ -67,6 +73,7 @@ impl proto::process_manager_server::ProcessManager for ProcessManagerService {
6773
&self,
6874
_request: Request<proto::GetStatusRequest>,
6975
) -> Result<Response<proto::GetStatusResponse>, Status> {
76+
self.ensure_idle().await?;
7077
let procs = self.mgr.processes().await;
7178
let total = procs.len() as u32;
7279
let (mut created, mut starting, mut running, mut stopping) = (0u32, 0, 0, 0);
@@ -103,6 +110,7 @@ impl proto::process_manager_server::ProcessManager for ProcessManagerService {
103110
request: Request<proto::CreateRequest>,
104111
) -> Result<Response<proto::CreateResponse>, Status> {
105112
require_privileged_pipe_client(&request)?;
113+
self.ensure_idle().await?;
106114
let req = request.into_inner();
107115
let config = create_request_to_config(&req)?;
108116
let (reply_tx, reply_rx) = oneshot::channel();
@@ -129,6 +137,7 @@ impl proto::process_manager_server::ProcessManager for ProcessManagerService {
129137
&self,
130138
request: Request<proto::StartRequest>,
131139
) -> Result<Response<proto::StartResponse>, Status> {
140+
self.ensure_idle().await?;
132141
let name_or_uuid = request.into_inner().name_or_uuid;
133142
let (reply_tx, reply_rx) = oneshot::channel();
134143
self.cmd_tx
@@ -154,6 +163,7 @@ impl proto::process_manager_server::ProcessManager for ProcessManagerService {
154163
&self,
155164
request: Request<proto::StopRequest>,
156165
) -> Result<Response<proto::StopResponse>, Status> {
166+
self.ensure_idle().await?;
157167
let name_or_uuid = request.into_inner().name_or_uuid;
158168
let (reply_tx, reply_rx) = oneshot::channel();
159169
self.cmd_tx
@@ -198,6 +208,7 @@ impl proto::process_manager_server::ProcessManager for ProcessManagerService {
198208
&self,
199209
_request: Request<proto::GetConfigRequest>,
200210
) -> Result<Response<proto::GetConfigResponse>, Status> {
211+
self.ensure_idle().await?;
201212
let procs = self.mgr.processes().await;
202213
let runtime = procs
203214
.iter()

pkg/procmgr/rust/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub mod env;
99
pub mod grpc;
1010
pub mod handle;
1111
pub mod manager;
12+
mod operation;
1213
pub mod ordering;
1314
pub mod platform;
1415
pub mod process;

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use super::{
44
};
55
use crate::command::{CreateResult, StartResult, StopResult};
66
use crate::config::{self, ConfigLoader, ProcessDefinition};
7+
use crate::operation::{OperationGate, OperationKind};
78
use crate::ordering;
89
use crate::process::ManagedProcess;
910
use crate::shutdown;
@@ -20,6 +21,7 @@ pub struct ProcessManager {
2021
pub(in crate::manager) startup_order: Arc<RwLock<Vec<usize>>>,
2122
pub(in crate::manager) config_loader: Arc<dyn ConfigLoader>,
2223
pub(in crate::manager) uuid_gen: Arc<dyn UuidGenerator>,
24+
pub(in crate::manager) operation_gate: OperationGate,
2325
}
2426

2527
impl ProcessManager {
@@ -38,9 +40,20 @@ impl ProcessManager {
3840
startup_order: Arc::new(RwLock::new(startup_result.order)),
3941
config_loader,
4042
uuid_gen,
43+
operation_gate: OperationGate::default(),
4144
}
4245
}
4346

47+
pub(crate) async fn ensure_idle(&self) -> Result<(), Status> {
48+
self.operation_gate.ensure_idle().await
49+
}
50+
51+
pub(in crate::manager) async fn force_begin_shutdown(&self) {
52+
self.operation_gate
53+
.force_begin(OperationKind::Shutdown)
54+
.await;
55+
}
56+
4457
/// Wrap this manager in a [`Supervisor`] for daemon execution.
4558
pub fn supervisor(self) -> Supervisor {
4659
Supervisor::new(self)

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use super::supervisor::RuntimeHandles;
22
use super::{ProcessManager, queue_restart, try_spawn_and_watch};
33
use crate::command::ReloadResult;
44
use crate::config::ProcessDefinition;
5+
use crate::operation::OperationKind;
56
use crate::process::{ManagedProcess, ProcessOrigin};
67
use crate::state::ProcessState;
78
use log::{info, warn};
@@ -122,6 +123,20 @@ impl ProcessManager {
122123
pub(crate) async fn handle_reload_config(
123124
&self,
124125
handles: &RuntimeHandles,
126+
) -> Result<ReloadResult, Status> {
127+
self.operation_gate
128+
.try_begin(OperationKind::ReloadConfig)
129+
.await?;
130+
let result = self.handle_reload_config_impl(handles).await;
131+
self.operation_gate
132+
.end(OperationKind::ReloadConfig)
133+
.await;
134+
result
135+
}
136+
137+
async fn handle_reload_config_impl(
138+
&self,
139+
handles: &RuntimeHandles,
125140
) -> Result<ReloadResult, Status> {
126141
let new_configs = self.config_loader.load();
127142

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ impl Supervisor {
118118

119119
let shutdown = platform::shutdown_signal();
120120
tokio::pin!(shutdown);
121-
run_manager_event_loop(
121+
let shutdown_requested = run_manager_event_loop(
122122
&manager,
123123
&handles,
124124
&mut cmd_rx,
@@ -128,6 +128,10 @@ impl Supervisor {
128128
)
129129
.await;
130130

131+
if shutdown_requested {
132+
manager.force_begin_shutdown().await;
133+
}
134+
131135
info!("dd-procmgrd shutting down");
132136

133137
let _ = grpc_shutdown_tx.send(());

pkg/procmgr/rust/src/operation.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2026-present Datadog, Inc.
5+
6+
use std::sync::Arc;
7+
8+
use tokio::sync::Mutex;
9+
use tonic::Status;
10+
11+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12+
pub(crate) enum OperationKind {
13+
ReloadConfig,
14+
Shutdown,
15+
}
16+
17+
#[derive(Clone, Default)]
18+
pub(crate) struct OperationGate {
19+
active: Arc<Mutex<Option<OperationKind>>>,
20+
}
21+
22+
impl OperationGate {
23+
pub async fn try_begin(&self, op: OperationKind) -> Result<(), Status> {
24+
let mut guard = self.active.lock().await;
25+
if let Some(active) = *guard {
26+
return Err(Status::failed_precondition(format!(
27+
"operation in progress ({active:?}); try again later"
28+
)));
29+
}
30+
*guard = Some(op);
31+
Ok(())
32+
}
33+
34+
pub async fn force_begin(&self, op: OperationKind) {
35+
*self.active.lock().await = Some(op);
36+
}
37+
38+
pub async fn end(&self, op: OperationKind) {
39+
let mut guard = self.active.lock().await;
40+
if *guard == Some(op) {
41+
*guard = None;
42+
}
43+
}
44+
45+
pub async fn ensure_idle(&self) -> Result<(), Status> {
46+
let guard = self.active.lock().await;
47+
if let Some(active) = *guard {
48+
return Err(Status::failed_precondition(format!(
49+
"operation in progress ({active:?}); try again later"
50+
)));
51+
}
52+
Ok(())
53+
}
54+
}
55+
56+
#[cfg(test)]
57+
mod tests {
58+
use super::*;
59+
60+
#[tokio::test]
61+
async fn test_operation_gate_blocks_while_active() {
62+
let gate = OperationGate::default();
63+
gate.try_begin(OperationKind::ReloadConfig)
64+
.await
65+
.expect("first begin");
66+
assert!(gate.ensure_idle().await.is_err());
67+
assert!(gate.try_begin(OperationKind::ReloadConfig).await.is_err());
68+
gate.end(OperationKind::ReloadConfig).await;
69+
gate.ensure_idle().await.expect("idle after end");
70+
}
71+
72+
#[tokio::test]
73+
async fn test_force_begin_shutdown_replaces_active_operation() {
74+
let gate = OperationGate::default();
75+
gate.try_begin(OperationKind::ReloadConfig)
76+
.await
77+
.expect("reload begin");
78+
gate.force_begin(OperationKind::Shutdown).await;
79+
assert!(gate.ensure_idle().await.is_err());
80+
assert!(gate.try_begin(OperationKind::ReloadConfig).await.is_err());
81+
}
82+
}

0 commit comments

Comments
 (0)