1use super::{InMemoryStateStore, ResilientStoreError, StateStore, StoreEvent, StoreOperation};
2use crate::error::StoreErrorKind;
3use crate::model::JobState;
4use std::collections::{HashMap, VecDeque};
5use std::sync::atomic::{AtomicBool, Ordering};
6use tokio::sync::Mutex;
7
8#[derive(Debug)]
12pub struct ResilientStateStore<S>
13where
14 S: StateStore,
15 S::Error: ResilientStoreError,
16{
17 primary: S,
18 degraded: AtomicBool,
19 mirror: InMemoryStateStore,
20 dirty: Mutex<HashMap<String, Option<JobState>>>,
21 events: Mutex<VecDeque<StoreEvent>>,
22}
23
24impl<S> ResilientStateStore<S>
25where
26 S: StateStore,
27 S::Error: ResilientStoreError,
28{
29 pub fn new(store: S) -> Self {
30 Self {
31 primary: store,
32 degraded: AtomicBool::new(false),
33 mirror: InMemoryStateStore::new(),
34 dirty: Mutex::new(HashMap::new()),
35 events: Mutex::new(VecDeque::new()),
36 }
37 }
38
39 pub fn degraded(store: S) -> Self {
40 Self {
41 primary: store,
42 degraded: AtomicBool::new(true),
43 mirror: InMemoryStateStore::new(),
44 dirty: Mutex::new(HashMap::new()),
45 events: Mutex::new(VecDeque::new()),
46 }
47 }
48
49 pub fn from_result(result: Result<S, S::Error>) -> Result<Self, S::Error> {
50 match result {
51 Ok(store) => Ok(Self::new(store)),
52 Err(error) if error.is_connection_issue() => Err(error),
53 Err(error) => Err(error),
54 }
55 }
56
57 pub fn is_degraded(&self) -> bool {
60 self.degraded.load(Ordering::SeqCst)
61 }
62
63 async fn load_mirror(&self, job_id: &str) -> Result<Option<JobState>, S::Error> {
64 match self.mirror.load(job_id).await {
65 Ok(state) => Ok(state),
66 Err(never) => match never {},
67 }
68 }
69
70 async fn save_mirror(&self, state: &JobState) -> Result<(), S::Error> {
71 match self.mirror.save(state).await {
72 Ok(()) => Ok(()),
73 Err(never) => match never {},
74 }
75 }
76
77 async fn delete_mirror(&self, job_id: &str) -> Result<(), S::Error> {
78 match self.mirror.delete(job_id).await {
79 Ok(()) => Ok(()),
80 Err(never) => match never {},
81 }
82 }
83
84 async fn sync_mirror(&self, job_id: &str, state: Option<&JobState>) -> Result<(), S::Error> {
85 match state {
86 Some(state) => self.save_mirror(state).await,
87 None => self.delete_mirror(job_id).await,
88 }
89 }
90
91 async fn mark_degraded(&self, operation: StoreOperation, error: &S::Error) {
92 let was_degraded = self.degraded.swap(true, Ordering::SeqCst);
93 if !was_degraded {
94 self.events.lock().await.push_back(StoreEvent::Degraded {
95 operation,
96 error: error.to_string(),
97 });
98 }
99 }
100
101 async fn mark_recovering(&self, operation: StoreOperation) {
102 self.events
103 .lock()
104 .await
105 .push_back(StoreEvent::Recovering { operation });
106 }
107
108 async fn mark_recovered(&self, operation: StoreOperation) {
109 let was_degraded = self.degraded.swap(false, Ordering::SeqCst);
110 if was_degraded {
111 self.events
112 .lock()
113 .await
114 .push_back(StoreEvent::Recovered { operation });
115 }
116 }
117
118 async fn mark_recovery_failed(&self, operation: StoreOperation, error: &S::Error) {
119 self.events
120 .lock()
121 .await
122 .push_back(StoreEvent::RecoveryFailed {
123 operation,
124 error: error.to_string(),
125 });
126 }
127
128 async fn record_dirty(&self, job_id: String, state: Option<JobState>) {
129 self.dirty.lock().await.insert(job_id, state);
130 }
131
132 async fn clear_dirty(&self, job_id: &str, operation: StoreOperation) {
133 let mut dirty = self.dirty.lock().await;
134 dirty.remove(job_id);
135 if dirty.is_empty() {
136 drop(dirty);
137 self.mark_recovered(operation).await;
138 }
139 }
140
141 async fn dirty_state(&self, job_id: &str) -> Option<Option<JobState>> {
142 self.dirty.lock().await.get(job_id).cloned()
143 }
144}
145
146impl<S> StateStore for ResilientStateStore<S>
147where
148 S: StateStore + Send + Sync,
149 S::Error: ResilientStoreError,
150{
151 type Error = S::Error;
152
153 async fn load(&self, job_id: &str) -> Result<Option<JobState>, Self::Error> {
154 if self.is_degraded() {
155 if let Some(dirty) = self.dirty_state(job_id).await {
156 return Ok(dirty);
157 }
158 }
159
160 match self.primary.load(job_id).await {
161 Ok(state) => {
162 self.sync_mirror(job_id, state.as_ref()).await?;
163 if self.is_degraded() {
164 self.mark_recovered(StoreOperation::Load).await;
165 }
166 Ok(state)
167 }
168 Err(error) if error.is_connection_issue() => {
169 self.mark_degraded(StoreOperation::Load, &error).await;
170 self.load_mirror(job_id).await
171 }
172 Err(error) => Err(error),
173 }
174 }
175
176 async fn save(&self, state: &JobState) -> Result<(), Self::Error> {
177 self.save_mirror(state).await?;
178 if self.is_degraded() {
179 self.record_dirty(state.job_id.clone(), Some(state.clone()))
180 .await;
181 self.mark_recovering(StoreOperation::Save).await;
182 }
183
184 match self.primary.save(state).await {
185 Ok(()) => {
186 self.clear_dirty(&state.job_id, StoreOperation::Save).await;
187 Ok(())
188 }
189 Err(error) if error.is_connection_issue() => {
190 self.mark_degraded(StoreOperation::Save, &error).await;
191 self.record_dirty(state.job_id.clone(), Some(state.clone()))
192 .await;
193 if self.is_degraded() {
194 self.mark_recovery_failed(StoreOperation::Save, &error)
195 .await;
196 }
197 Ok(())
198 }
199 Err(error) => Err(error),
200 }
201 }
202
203 async fn delete(&self, job_id: &str) -> Result<(), Self::Error> {
204 self.delete_mirror(job_id).await?;
205 if self.is_degraded() {
206 self.record_dirty(job_id.to_string(), None).await;
207 self.mark_recovering(StoreOperation::Delete).await;
208 }
209
210 match self.primary.delete(job_id).await {
211 Ok(()) => {
212 self.clear_dirty(job_id, StoreOperation::Delete).await;
213 Ok(())
214 }
215 Err(error) if error.is_connection_issue() => {
216 self.mark_degraded(StoreOperation::Delete, &error).await;
217 self.record_dirty(job_id.to_string(), None).await;
218 if self.is_degraded() {
219 self.mark_recovery_failed(StoreOperation::Delete, &error)
220 .await;
221 }
222 Ok(())
223 }
224 Err(error) => Err(error),
225 }
226 }
227
228 async fn drain_events(&self) -> Result<Vec<StoreEvent>, Self::Error> {
229 let mut events = self.events.lock().await;
230 Ok(events.drain(..).collect())
231 }
232
233 fn classify_error(error: &Self::Error) -> StoreErrorKind
234 where
235 Self: Sized,
236 {
237 S::classify_error(error)
238 }
239}