hydracache_server/
upgrade.rs1use serde::Serialize;
2use thiserror::Error;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
6#[serde(rename_all = "snake_case")]
7pub enum UpgradeStrategy {
8 InheritedSocket,
10 ReusePort,
12}
13
14impl UpgradeStrategy {
15 pub fn platform_default() -> Self {
17 if cfg!(windows) {
18 Self::ReusePort
19 } else {
20 Self::InheritedSocket
21 }
22 }
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
27#[serde(rename_all = "snake_case")]
28pub enum UpgradePhase {
29 Prepared,
31 NewReady,
33 OldDraining,
35 Complete,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct UpgradePlan {
42 generation: u64,
43 member_id: String,
44 strategy: UpgradeStrategy,
45}
46
47impl UpgradePlan {
48 pub fn new(generation: u64, member_id: impl Into<String>) -> Self {
50 Self {
51 generation,
52 member_id: member_id.into(),
53 strategy: UpgradeStrategy::platform_default(),
54 }
55 }
56
57 pub fn with_strategy(mut self, strategy: UpgradeStrategy) -> Self {
59 self.strategy = strategy;
60 self
61 }
62
63 pub fn prepare(self) -> Result<GracefulUpgrade, UpgradeError> {
65 if self.generation == 0 {
66 return Err(UpgradeError::InvalidGeneration);
67 }
68 if self.member_id.trim().is_empty() {
69 return Err(UpgradeError::MissingMemberId);
70 }
71 Ok(GracefulUpgrade {
72 plan: self,
73 phase: UpgradePhase::Prepared,
74 old_accepting: true,
75 new_ready: false,
76 in_flight: 0,
77 completed: 0,
78 })
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct GracefulUpgrade {
85 plan: UpgradePlan,
86 phase: UpgradePhase,
87 old_accepting: bool,
88 new_ready: bool,
89 in_flight: usize,
90 completed: usize,
91}
92
93impl GracefulUpgrade {
94 pub fn mark_new_ready(&mut self) {
96 self.new_ready = true;
97 self.phase = UpgradePhase::NewReady;
98 }
99
100 pub fn start_draining_old(&mut self) -> Result<(), UpgradeError> {
102 if !self.new_ready {
103 return Err(UpgradeError::ReplacementNotReady);
104 }
105 self.old_accepting = false;
106 self.phase = UpgradePhase::OldDraining;
107 Ok(())
108 }
109
110 pub fn record_request(&mut self) -> bool {
112 if !self.old_accepting {
113 return false;
114 }
115 self.in_flight = self.in_flight.saturating_add(1);
116 true
117 }
118
119 pub fn finish_request(&mut self) {
121 if self.in_flight > 0 {
122 self.in_flight -= 1;
123 self.completed = self.completed.saturating_add(1);
124 }
125 }
126
127 pub fn complete(mut self) -> Result<UpgradeReport, UpgradeError> {
129 if !self.new_ready {
130 return Err(UpgradeError::ReplacementNotReady);
131 }
132 if self.in_flight > 0 {
133 return Err(UpgradeError::InFlightRequestsRemaining(self.in_flight));
134 }
135 self.phase = UpgradePhase::Complete;
136 Ok(UpgradeReport {
137 generation: self.plan.generation,
138 member_id: self.plan.member_id,
139 strategy: self.plan.strategy,
140 phase: self.phase,
141 completed_requests: self.completed,
142 dropped_requests: 0,
143 })
144 }
145
146 pub fn phase(&self) -> UpgradePhase {
148 self.phase
149 }
150
151 pub fn membership_stable(&self) -> bool {
153 !self.plan.member_id.trim().is_empty()
154 }
155
156 pub fn in_flight(&self) -> usize {
158 self.in_flight
159 }
160
161 pub fn old_accepting(&self) -> bool {
163 self.old_accepting
164 }
165}
166
167#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
169pub struct UpgradeReport {
170 pub generation: u64,
172 pub member_id: String,
174 pub strategy: UpgradeStrategy,
176 pub phase: UpgradePhase,
178 pub completed_requests: usize,
180 pub dropped_requests: usize,
182}
183
184#[derive(Debug, Error, PartialEq, Eq)]
186pub enum UpgradeError {
187 #[error("upgrade generation must be greater than zero")]
189 InvalidGeneration,
190 #[error("upgrade requires a non-empty member id")]
192 MissingMemberId,
193 #[error("replacement process is not ready")]
195 ReplacementNotReady,
196 #[error("{0} in-flight request(s) remain during upgrade")]
198 InFlightRequestsRemaining(usize),
199}