appcore_api/http/
reload_generation.rs1use arc_swap::ArcSwap;
14use axum::Router;
15use parking_lot::Mutex;
16use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
17use std::sync::{Arc, Weak};
18
19pub(super) const MAX_RETAINED_GENERATIONS: usize = 2;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23#[non_exhaustive]
24pub struct HttpRoutingGenerationSnapshot {
25 pub generation: u64,
27 pub accepting: bool,
29 pub inflight: usize,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[non_exhaustive]
36pub struct HttpRoutingGenerationsSnapshot {
37 pub active: HttpRoutingGenerationSnapshot,
39 pub retiring: Option<HttpRoutingGenerationSnapshot>,
41 pub retained_generations: usize,
43 pub max_retained_generations: usize,
45}
46
47pub(super) struct RoutingGeneration {
48 id: u64,
49 router: Router,
50 accepting: AtomicBool,
51 inflight: AtomicUsize,
52 retiring_slot: Weak<RetiringGenerationSlot>,
53}
54
55impl RoutingGeneration {
56 fn new(id: u64, router: Router, retiring_slot: Weak<RetiringGenerationSlot>) -> Self {
57 Self {
58 id,
59 router,
60 accepting: AtomicBool::new(true),
61 inflight: AtomicUsize::new(0),
62 retiring_slot,
63 }
64 }
65
66 pub(super) fn id(&self) -> u64 {
67 self.id
68 }
69
70 pub(super) fn router(&self) -> &Router {
71 &self.router
72 }
73
74 pub(super) fn inflight(&self) -> usize {
75 self.inflight.load(Ordering::Acquire)
76 }
77
78 pub(super) fn start_accepting(&self) {
79 self.accepting.store(true, Ordering::Release);
80 }
81
82 pub(super) fn stop_accepting(&self) {
83 self.accepting.store(false, Ordering::Release);
84 }
85
86 pub(super) fn try_admit(self: &Arc<Self>) -> Option<RoutingPermit> {
87 if !self.accepting.load(Ordering::Acquire) {
88 return None;
89 }
90 self.inflight
91 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |current| {
92 current.checked_add(1)
93 })
94 .ok()?;
95 if self.accepting.load(Ordering::Acquire) {
96 return Some(RoutingPermit {
97 generation: Arc::clone(self),
98 });
99 }
100 self.release_request();
101 None
102 }
103
104 fn release_request(&self) {
105 let Ok(previous) =
106 self.inflight
107 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |current| {
108 current.checked_sub(1)
109 })
110 else {
111 debug_assert!(false, "routing generation in-flight underflow");
112 return;
113 };
114 if previous == 1 && !self.accepting.load(Ordering::Acquire) {
115 if let Some(slot) = self.retiring_slot.upgrade() {
116 slot.clear_if_drained(self.id);
117 }
118 }
119 }
120
121 fn snapshot(&self) -> HttpRoutingGenerationSnapshot {
122 HttpRoutingGenerationSnapshot {
123 generation: self.id,
124 accepting: self.accepting.load(Ordering::Acquire),
125 inflight: self.inflight(),
126 }
127 }
128}
129
130pub(super) struct RoutingPermit {
131 generation: Arc<RoutingGeneration>,
132}
133
134impl Drop for RoutingPermit {
135 fn drop(&mut self) {
136 self.generation.release_request();
137 }
138}
139
140struct RetiringGenerationSlot {
141 generation: Mutex<Option<Arc<RoutingGeneration>>>,
142}
143
144impl RetiringGenerationSlot {
145 fn new() -> Self {
146 Self {
147 generation: Mutex::new(None),
148 }
149 }
150
151 fn retire(&self, generation: Arc<RoutingGeneration>) -> bool {
152 let mut slot = self.generation.lock();
153 if slot.is_some() {
154 return false;
155 }
156 *slot = Some(generation);
157 true
158 }
159
160 fn clear(&self, generation: u64) {
161 let mut slot = self.generation.lock();
162 if slot
163 .as_ref()
164 .is_some_and(|current| current.id == generation)
165 {
166 *slot = None;
167 }
168 }
169
170 fn clear_if_drained(&self, generation: u64) {
171 let mut slot = self.generation.lock();
172 if slot
173 .as_ref()
174 .is_some_and(|current| current.id == generation && current.inflight() == 0)
175 {
176 *slot = None;
177 }
178 }
179
180 fn snapshot(&self) -> Option<HttpRoutingGenerationSnapshot> {
181 let slot = self.generation.lock();
182 slot.as_ref().map(|generation| generation.snapshot())
183 }
184
185 fn clear_drained(&self) {
186 let mut slot = self.generation.lock();
187 if slot
188 .as_ref()
189 .is_some_and(|generation| generation.inflight() == 0)
190 {
191 *slot = None;
192 }
193 }
194}
195
196pub(super) struct RoutingTable {
197 active: ArcSwap<RoutingGeneration>,
198 retiring: Arc<RetiringGenerationSlot>,
199}
200
201impl RoutingTable {
202 pub(super) fn new(id: u64, router: Router) -> Self {
203 let retiring = Arc::new(RetiringGenerationSlot::new());
204 let active = Arc::new(RoutingGeneration::new(
205 id,
206 router,
207 Arc::downgrade(&retiring),
208 ));
209 Self {
210 active: ArcSwap::from(active),
211 retiring,
212 }
213 }
214
215 pub(super) fn active(&self) -> Arc<RoutingGeneration> {
216 self.active.load_full()
217 }
218
219 pub(super) fn generation(&self, id: u64, router: Router) -> Arc<RoutingGeneration> {
220 Arc::new(RoutingGeneration::new(
221 id,
222 router,
223 Arc::downgrade(&self.retiring),
224 ))
225 }
226
227 pub(super) fn activate(&self, generation: Arc<RoutingGeneration>) {
228 self.active.store(generation);
229 }
230
231 pub(super) fn retire(&self, generation: Arc<RoutingGeneration>) -> bool {
232 self.retiring.retire(generation)
233 }
234
235 pub(super) fn release_retiring(&self, generation: u64) {
236 self.retiring.clear(generation);
237 }
238
239 pub(super) fn release_drained_retiring(&self) {
240 self.retiring.clear_drained();
241 }
242
243 pub(super) fn generations_snapshot(&self) -> HttpRoutingGenerationsSnapshot {
244 let active = self.active();
245 let active_snapshot = active.snapshot();
246 let retiring = self
247 .retiring
248 .snapshot()
249 .filter(|snapshot| snapshot.generation != active_snapshot.generation);
250 HttpRoutingGenerationsSnapshot {
251 active: active_snapshot,
252 retiring,
253 retained_generations: 1 + usize::from(retiring.is_some()),
254 max_retained_generations: MAX_RETAINED_GENERATIONS,
255 }
256 }
257}