oxirs_stream/state/
linearizable_reader.rs1use std::sync::Arc;
39
40use serde::{Deserialize, Serialize};
41use thiserror::Error;
42use tracing::debug;
43
44use oxirs_cluster::consensus::ConsensusManager;
45
46use super::raft_state::{RaftBackedOperatorState, StateValue};
47
48#[derive(Debug, Error)]
52pub enum LinearizableReadError {
53 #[error("local node is not the leader")]
55 NotLeader,
56 #[error("state error: {0}")]
58 State(String),
59}
60
61pub type LinearizableReadResult<T> = std::result::Result<T, LinearizableReadError>;
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct LinearizableReadConfig {
69 pub require_leader: bool,
73}
74
75impl Default for LinearizableReadConfig {
76 fn default() -> Self {
77 Self {
78 require_leader: true,
79 }
80 }
81}
82
83pub struct LinearizableReader {
85 consensus: Arc<ConsensusManager>,
86 state: Arc<RaftBackedOperatorState>,
87 config: LinearizableReadConfig,
88}
89
90impl LinearizableReader {
91 pub fn new(
93 consensus: Arc<ConsensusManager>,
94 state: Arc<RaftBackedOperatorState>,
95 config: LinearizableReadConfig,
96 ) -> Self {
97 Self {
98 consensus,
99 state,
100 config,
101 }
102 }
103
104 pub async fn is_leader(&self) -> bool {
106 self.consensus.is_leader().await
107 }
108
109 pub async fn term(&self) -> u64 {
111 self.consensus.current_term().await
112 }
113
114 pub async fn get(&self, key: &str) -> LinearizableReadResult<Option<StateValue>> {
122 let term = self.consensus.current_term().await;
123 if self.config.require_leader && !self.consensus.is_leader().await {
124 return Err(LinearizableReadError::NotLeader);
125 }
126 debug!(term, key, "linearizable read served (sticky)");
127 Ok(self.state.get_local(key))
128 }
129
130 pub async fn get_with_barrier(&self, key: &str) -> LinearizableReadResult<Option<StateValue>> {
139 if self.config.require_leader && !self.consensus.is_leader().await {
140 return Err(LinearizableReadError::NotLeader);
141 }
142 let tx_id = format!("oxirs-stream-barrier-{}", uuid::Uuid::new_v4());
143 self.consensus
147 .begin_transaction(tx_id.clone())
148 .await
149 .map_err(|e| LinearizableReadError::State(e.to_string()))?;
150 self.consensus
151 .rollback_transaction(tx_id)
152 .await
153 .map_err(|e| LinearizableReadError::State(e.to_string()))?;
154 Ok(self.state.get_local(key))
155 }
156
157 pub fn state(&self) -> &Arc<RaftBackedOperatorState> {
159 &self.state
160 }
161
162 pub fn consensus(&self) -> &Arc<ConsensusManager> {
164 &self.consensus
165 }
166}
167
168#[cfg(test)]
171mod tests {
172 use super::*;
173 use crate::state::raft_state::{RaftBackedOperatorState, RaftStateConfig, StateValue};
174 use async_trait::async_trait;
175 use oxirs_cluster::stream_integration::StreamMessage;
176 use oxirs_cluster::streaming::cluster_sink::{SinkError, StreamSink};
177 use parking_lot::Mutex;
178
179 #[derive(Default)]
180 struct PassThroughSink {
181 committed: Mutex<u64>,
182 }
183
184 #[async_trait]
185 impl StreamSink for PassThroughSink {
186 async fn write_batch(&self, _events: Vec<StreamMessage>) -> Result<(), SinkError> {
187 *self.committed.lock() += 1;
188 Ok(())
189 }
190 }
191
192 #[tokio::test]
193 async fn read_returns_locally_cached_value_when_not_requiring_leader() {
194 let consensus = Arc::new(ConsensusManager::new(1, vec![]));
195 let sink = Arc::new(PassThroughSink::default());
196 let state = Arc::new(RaftBackedOperatorState::new(
197 RaftStateConfig {
198 operator_id: "lin-test".into(),
199 stream_id: None,
200 },
201 sink,
202 ));
203 state.put("k", StateValue::Counter(11)).await.expect("put");
204
205 let reader = LinearizableReader::new(
206 consensus,
207 state,
208 LinearizableReadConfig {
209 require_leader: false,
210 },
211 );
212 let v = reader.get("k").await.expect("ok");
213 assert_eq!(v, Some(StateValue::Counter(11)));
214 }
215
216 #[tokio::test]
217 async fn missing_key_returns_none() {
218 let consensus = Arc::new(ConsensusManager::new(2, vec![]));
219 let sink = Arc::new(PassThroughSink::default());
220 let state = Arc::new(RaftBackedOperatorState::new(
221 RaftStateConfig {
222 operator_id: "lin-test".into(),
223 stream_id: None,
224 },
225 sink,
226 ));
227 let reader = LinearizableReader::new(
228 consensus,
229 state,
230 LinearizableReadConfig {
231 require_leader: false,
232 },
233 );
234 let v = reader.get("ghost").await.expect("ok");
235 assert!(v.is_none());
236 }
237
238 #[tokio::test]
239 async fn get_with_barrier_succeeds_on_leader_after_put() {
240 use oxirs_cluster::raft::{init_global_shared_storage, reset_global_shared_storage};
241 static TEST_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
242 let _g = TEST_LOCK.lock().await;
243 init_global_shared_storage();
244 reset_global_shared_storage().await;
245
246 let consensus = Arc::new(ConsensusManager::new(11, vec![]));
247 let sink = Arc::new(PassThroughSink::default());
248 let state = Arc::new(RaftBackedOperatorState::new(
249 RaftStateConfig {
250 operator_id: "lin-barrier".into(),
251 stream_id: None,
252 },
253 sink,
254 ));
255 state
256 .put("counter", StateValue::Counter(7))
257 .await
258 .expect("put");
259
260 let reader = LinearizableReader::new(
261 consensus,
262 state,
263 LinearizableReadConfig {
264 require_leader: true,
265 },
266 );
267 let v = reader.get_with_barrier("counter").await.expect("ok");
268 assert_eq!(v, Some(StateValue::Counter(7)));
269 }
270
271 #[tokio::test]
272 async fn require_leader_true_serves_when_local_is_leader() {
273 let consensus = Arc::new(ConsensusManager::new(10, vec![]));
276 let sink = Arc::new(PassThroughSink::default());
277 let state = Arc::new(RaftBackedOperatorState::new(
278 RaftStateConfig {
279 operator_id: "lin-test".into(),
280 stream_id: None,
281 },
282 sink,
283 ));
284 state.put("k", StateValue::Counter(99)).await.expect("put");
285 let reader = LinearizableReader::new(
286 consensus,
287 state,
288 LinearizableReadConfig {
289 require_leader: true,
290 },
291 );
292 assert!(reader.is_leader().await);
293 let v = reader.get("k").await.expect("ok");
294 assert_eq!(v, Some(StateValue::Counter(99)));
295 }
296
297 #[tokio::test]
298 async fn term_query_returns_finite_value() {
299 let consensus = Arc::new(ConsensusManager::new(3, vec![]));
300 let sink = Arc::new(PassThroughSink::default());
301 let state = Arc::new(RaftBackedOperatorState::new(
302 RaftStateConfig {
303 operator_id: "lin-test".into(),
304 stream_id: None,
305 },
306 sink,
307 ));
308 let reader = LinearizableReader::new(consensus, state, LinearizableReadConfig::default());
309 let _ = reader.term().await;
311 }
312}