Skip to content

Commit c54cb43

Browse files
author
developerworks
committed
Wire dashboard IPC security pipeline with bounded frame reader
- Wire IpcSecurityPipeline into dashboard_service via ValidatedDashboardIpcConfig.security_config - Replace read_line with bounded frame reader (DEFAULT_MAX_FRAME_BYTES=1MiB) to reject oversized requests before JSON parsing - Extract real peer credentials (pid/uid/gid) via SO_PEERCRED instead of placeholder values - Generate unique per-connection connection_id for rate limiting isolation - Fix C8 idempotency: return cached response on hit, cache result after dispatch - Fix C7 audit: write audit for all paths (deny, cache-hit, dispatch-ok, dispatch-err); high-risk commands fail closed on audit write failure - Propagate real raw_body_len to C5 size limit check - Make write_audit return Result for caller-driven failure strategy
1 parent 3f92078 commit c54cb43

21 files changed

Lines changed: 3697 additions & 97 deletions

docs/project-archtecture.excalidraw

Lines changed: 2371 additions & 0 deletions
Large diffs are not rendered by default.

examples/backpressure_demo.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
//! Demonstrates backpressure strategies for slow event subscribers.
2+
//!
3+
//! The supervisor supports two backpressure strategies:
4+
//! - AlertAndBlock: warns at soft threshold, blocks producer at hard threshold.
5+
//! - SampleAndAudit: samples and discards events when buffers fill up, records
6+
//! the discard ratio in the audit trail.
7+
//!
8+
//! This example constructs both strategy configurations and shows how buffer
9+
//! occupancy thresholds trigger alerts and degradation.
10+
11+
use rust_supervisor::observe::pipeline::{ObservabilityPipeline, TestRecorder};
12+
use rust_supervisor::spec::supervisor::{BackpressureConfig, BackpressureStrategy};
13+
14+
/// Runs the backpressure demonstration.
15+
fn main() {
16+
println!("=== Backpressure Strategy Demo ===");
17+
println!();
18+
19+
// Build two backpressure configurations.
20+
let alert_block = BackpressureConfig {
21+
strategy: BackpressureStrategy::AlertAndBlock,
22+
warn_threshold_pct: 80,
23+
critical_threshold_pct: 95,
24+
window_secs: 30,
25+
audit_channel_capacity: 1024,
26+
};
27+
28+
let sample_audit = BackpressureConfig {
29+
strategy: BackpressureStrategy::SampleAndAudit,
30+
warn_threshold_pct: 70,
31+
critical_threshold_pct: 90,
32+
window_secs: 30,
33+
audit_channel_capacity: 2048,
34+
};
35+
36+
println!("--- AlertAndBlock (default) ---");
37+
println!(
38+
" warn_threshold = {}%",
39+
alert_block.warn_threshold_pct
40+
);
41+
println!(
42+
" critical_threshold = {}%",
43+
alert_block.critical_threshold_pct
44+
);
45+
println!(" window = {}s", alert_block.window_secs);
46+
println!(
47+
" audit_capacity = {}",
48+
alert_block.audit_channel_capacity
49+
);
50+
println!(" behavior at warn: emit backpressure alert");
51+
println!(" behavior at crit: block producer until subscriber catches up");
52+
println!();
53+
54+
println!("--- SampleAndAudit ---");
55+
println!(
56+
" warn_threshold = {}%",
57+
sample_audit.warn_threshold_pct
58+
);
59+
println!(
60+
" critical_threshold = {}%",
61+
sample_audit.critical_threshold_pct
62+
);
63+
println!(" window = {}s", sample_audit.window_secs);
64+
println!(
65+
" audit_capacity = {}",
66+
sample_audit.audit_channel_capacity
67+
);
68+
println!(" behavior at warn: start sampling events, record ratio in audit trail");
69+
println!(" behavior at crit: increase sampling rate, record degradation");
70+
println!();
71+
72+
// Demonstrate building an observability pipeline.
73+
println!("--- Pipeline Construction ---");
74+
println!();
75+
76+
let _pipeline = ObservabilityPipeline::new(16, 16);
77+
println!(" pipeline created with journal_capacity=16, subscriber_capacity=16");
78+
79+
// Pipeline is immutable for external callers; subscribers are added
80+
// during construction. The pipeline owns its journal and subscriber list.
81+
82+
// Demonstrate TestRecorder for recording backpressure events.
83+
println!();
84+
println!("--- TestRecorder (lag recording) ---");
85+
println!();
86+
87+
let mut recorder = TestRecorder::new();
88+
recorder.record_lag(5);
89+
println!(" recorded subscriber lag of 5 events");
90+
91+
println!();
92+
println!("=== Summary ===");
93+
println!("AlertAndBlock -> safe default, never drops events, blocks producers.");
94+
println!("SampleAndAudit -> production choice under high volume, drops under pressure.");
95+
println!("Both strategies emit BackpressureAlert and BackpressureDegradation events.");
96+
}
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
//! Demonstrates generation fencing mechanics during manual restart.
2+
//!
3+
//! When a restart request arrives while a child still has an active attempt,
4+
//! the runtime enters a fencing sequence:
5+
//! 1. Open -> WaitingForOldStop (cancel delivered)
6+
//! 2. WaitingForOldStop -> AbortingOld (grace deadline expired)
7+
//! 3. AbortingOld -> ReadyToStart (old attempt confirmed finished)
8+
//! 4. ReadyToStart -> Open (new generation started)
9+
//!
10+
//! This example constructs fence states, decisions, and outcomes to illustrate
11+
//! the lifecycle without requiring an actual running supervisor.
12+
13+
use rust_supervisor::child_runner::run_exit::TaskExit;
14+
use rust_supervisor::control::outcome::{
15+
ChildControlFailure, ChildControlFailurePhase, GenerationFenceDecision, GenerationFenceOutcome,
16+
GenerationFencePhase, GenerationFenceState, PendingRestart, StaleAttemptReport,
17+
StaleReportHandling,
18+
};
19+
use rust_supervisor::id::types::{ChildId, ChildStartCount, Generation};
20+
use uuid::Uuid;
21+
22+
/// Runs the generation fencing demonstration.
23+
fn main() {
24+
println!("=== Generation Fencing Demo ===");
25+
println!();
26+
27+
let child_id = ChildId::new("order_processor");
28+
let command_id = Uuid::nil();
29+
30+
// Phase 0: Open — no active attempt, no fence.
31+
println!("--- Phase: Open ---");
32+
let fence_open = GenerationFencePhase::Open;
33+
println!(" {fence_open:?} - No active attempt, restart allowed immediately");
34+
println!();
35+
36+
// Phase 1: WaitingForOldStop — restart queued behind an active attempt.
37+
println!("--- Phase: WaitingForOldStop ---");
38+
let fence_waiting = GenerationFencePhase::WaitingForOldStop;
39+
let pending = PendingRestart::new(
40+
command_id,
41+
"operator",
42+
"manual restart for deployment",
43+
Generation { value: 2 },
44+
ChildStartCount { value: 3 },
45+
Generation { value: 3 },
46+
1000, // requested_at_unix_nanos
47+
5000, // stop_deadline_at_unix_nanos
48+
false, // abort_requested
49+
0, // duplicate_request_count
50+
);
51+
println!(" {fence_waiting:?} - Restart queued, waiting for old attempt to stop");
52+
println!(
53+
" pending: old_gen={:?} old_attempt={:?} target_gen={:?}",
54+
pending.old_generation.value, pending.old_attempt.value, pending.target_generation.value,
55+
);
56+
println!();
57+
58+
// Phase 2: AbortingOld — graceful deadline exceeded, abort sent.
59+
println!("--- Phase: AbortingOld ---");
60+
let fence_aborting = GenerationFencePhase::AbortingOld;
61+
println!(" {fence_aborting:?} - Graceful stop deadline exceeded, abort requested");
62+
println!();
63+
64+
// Phase 3: ReadyToStart — old attempt confirmed exited.
65+
println!("--- Phase: ReadyToStart ---");
66+
let fence_ready = GenerationFencePhase::ReadyToStart;
67+
println!(" {fence_ready:?} - Old attempt confirmed finished, new generation may start");
68+
println!();
69+
70+
// Phase 4: Closed — supervisor shutting down.
71+
println!("--- Phase: Closed ---");
72+
let fence_closed = GenerationFencePhase::Closed;
73+
println!(" {fence_closed:?} - Supervisor shutting down, restart blocked");
74+
println!();
75+
76+
// Demonstrate fence decisions.
77+
println!("=== Fence Decisions ===");
78+
println!();
79+
80+
let decisions: [(GenerationFenceDecision, &str); 5] = [
81+
(
82+
GenerationFenceDecision::StartedImmediately,
83+
"No active attempt; new generation started at once",
84+
),
85+
(
86+
GenerationFenceDecision::QueuedAfterStop,
87+
"Active attempt present; restart queued behind it",
88+
),
89+
(
90+
GenerationFenceDecision::AlreadyPending,
91+
"Duplicate restart merged into existing pending request",
92+
),
93+
(
94+
GenerationFenceDecision::BlockedByShutdown,
95+
"Supervisor is shutting down; restart rejected",
96+
),
97+
(
98+
GenerationFenceDecision::Rejected,
99+
"Request rejected; see conflict field for reason",
100+
),
101+
];
102+
103+
for (decision, description) in &decisions {
104+
println!(" {decision:?}");
105+
println!(" -> {description}");
106+
}
107+
println!();
108+
109+
// Build a complete fence outcome.
110+
println!("=== Complete Fence Outcome ===");
111+
println!();
112+
113+
let outcome = GenerationFenceOutcome::new(
114+
GenerationFenceDecision::QueuedAfterStop,
115+
Some(Generation { value: 2 }),
116+
Some(ChildStartCount { value: 3 }),
117+
Some(Generation { value: 3 }),
118+
true, // cancel_delivered
119+
false, // abort_requested
120+
None, // no conflict
121+
);
122+
123+
println!(" decision = {:?}", outcome.decision);
124+
println!(
125+
" old_generation = {:?}",
126+
outcome.old_generation.map(|g| g.value)
127+
);
128+
println!(
129+
" old_attempt = {:?}",
130+
outcome.old_attempt.map(|a| a.value)
131+
);
132+
println!(
133+
" target_generation = {:?}",
134+
outcome.target_generation.map(|g| g.value)
135+
);
136+
println!(" cancel_delivered = {}", outcome.cancel_delivered);
137+
println!(" abort_requested = {}", outcome.abort_requested);
138+
println!(" conflict = {:?}", outcome.conflict);
139+
println!();
140+
141+
// Build a rejected outcome with conflict detail.
142+
println!("=== Rejected Outcome with Conflict ===");
143+
println!();
144+
145+
let conflict = ChildControlFailure::new(
146+
ChildControlFailurePhase::WaitCompletion,
147+
"child is already being stopped by another command",
148+
true, // recoverable
149+
);
150+
151+
let rejected = GenerationFenceOutcome::new(
152+
GenerationFenceDecision::Rejected,
153+
Some(Generation { value: 2 }),
154+
Some(ChildStartCount { value: 3 }),
155+
None,
156+
false,
157+
false,
158+
Some(conflict),
159+
);
160+
161+
println!(" decision = {:?}", rejected.decision);
162+
println!(
163+
" conflict.phase = {:?}",
164+
rejected.conflict.as_ref().map(|c| c.phase)
165+
);
166+
println!(
167+
" conflict.reason = {:?}",
168+
rejected.conflict.as_ref().map(|c| &c.reason)
169+
);
170+
println!(
171+
" conflict.recoverable = {:?}",
172+
rejected.conflict.as_ref().map(|c| c.recoverable)
173+
);
174+
println!();
175+
176+
// Demonstrate stale report handling.
177+
println!("=== Stale Attempt Report ===");
178+
println!();
179+
180+
let stale = StaleAttemptReport::new(
181+
child_id,
182+
Generation { value: 1 },
183+
ChildStartCount { value: 2 },
184+
Some(Generation { value: 3 }),
185+
Some(ChildStartCount { value: 1 }),
186+
TaskExit::Succeeded,
187+
StaleReportHandling::IgnoredForState,
188+
1000,
189+
);
190+
191+
println!(
192+
" A late completion report from gen={} attempt={} arrived after",
193+
stale.reported_generation.value, stale.reported_attempt.value,
194+
);
195+
println!(
196+
" the runtime had moved to gen={:?} attempt={:?}.",
197+
stale.current_generation.map(|g| g.value),
198+
stale.current_attempt.map(|a| a.value),
199+
);
200+
println!(" handled_as = {:?}", stale.handled_as);
201+
println!();
202+
203+
// Demonstrate GenerationFenceState.
204+
println!("=== GenerationFenceState Timeline ===");
205+
println!();
206+
207+
let states: [(GenerationFencePhase, &str); 5] = [
208+
(GenerationFencePhase::Open, "initial state, no fence"),
209+
(
210+
GenerationFencePhase::WaitingForOldStop,
211+
"restart accepted, waiting for old attempt",
212+
),
213+
(
214+
GenerationFencePhase::AbortingOld,
215+
"grace deadline expired, aborting",
216+
),
217+
(
218+
GenerationFencePhase::ReadyToStart,
219+
"old attempt done, starting new generation",
220+
),
221+
(
222+
GenerationFencePhase::Closed,
223+
"supervisor shutting down, no restarts",
224+
),
225+
];
226+
227+
for (phase, desc) in &states {
228+
let state = GenerationFenceState {
229+
phase: *phase,
230+
active_generation: None,
231+
active_attempt: None,
232+
pending_restart: None,
233+
last_stale_report: None,
234+
};
235+
println!(" {:20} - {}", format!("{:?}", state.phase), desc);
236+
}
237+
238+
println!();
239+
println!("=== Summary ===");
240+
println!("Generation fencing ensures at-most-one active attempt per child.");
241+
println!("Restart requests queue behind the active attempt and wait for it to stop.");
242+
println!(
243+
"Late (stale) reports from old generations are ignored for state but recorded for audit."
244+
);
245+
}

0 commit comments

Comments
 (0)