1use std::future::Future;
4use std::sync::Arc;
5use std::sync::atomic::{AtomicUsize, Ordering};
6use std::time::Duration;
7
8use crafty_net::send_client_request;
9use crafty_net::transport::Transport;
10use crafty_proto::{ClientRequest, ClientResponse, NodeId};
11
12use crate::error::ClientError;
13
14pub trait Client {
21 fn propose(
24 &self,
25 payload: Vec<u8>,
26 ) -> impl Future<Output = Result<Vec<u8>, ClientError>> + Send;
27
28 fn query(&self, payload: Vec<u8>) -> impl Future<Output = Result<Vec<u8>, ClientError>> + Send;
31}
32
33pub trait KeyedClient: Client {
35 fn propose_keyed(
37 &self,
38 key: Vec<u8>,
39 payload: Vec<u8>,
40 ) -> impl Future<Output = Result<Vec<u8>, ClientError>> + Send;
41
42 fn query_keyed(
44 &self,
45 key: Vec<u8>,
46 payload: Vec<u8>,
47 ) -> impl Future<Output = Result<Vec<u8>, ClientError>> + Send;
48}
49
50#[derive(Debug, Clone)]
52pub struct RetryPolicy {
53 pub max_attempts: u32,
56 pub attempt_timeout: Duration,
59 pub backoff: Duration,
61}
62
63impl Default for RetryPolicy {
64 fn default() -> Self {
65 Self {
66 max_attempts: 5,
67 attempt_timeout: Duration::from_secs(5),
68 backoff: Duration::from_millis(100),
69 }
70 }
71}
72
73pub struct RemoteClient {
83 transport: Arc<dyn Transport>,
84 targets: Vec<NodeId>,
85 retry: RetryPolicy,
86 cursor: AtomicUsize,
87}
88
89impl RemoteClient {
90 #[must_use]
94 pub fn new(transport: Arc<dyn Transport>, targets: impl IntoIterator<Item = NodeId>) -> Self {
95 Self {
96 transport,
97 targets: targets.into_iter().collect(),
98 retry: RetryPolicy::default(),
99 cursor: AtomicUsize::new(0),
100 }
101 }
102
103 #[must_use]
105 pub fn with_retry(mut self, retry: RetryPolicy) -> Self {
106 self.retry = retry;
107 self
108 }
109
110 #[must_use]
112 pub fn targets(&self) -> &[NodeId] {
113 &self.targets
114 }
115
116 async fn call(&self, request: ClientRequest) -> Result<Vec<u8>, ClientError> {
118 let n = self.targets.len();
119 if n == 0 {
120 return Err(ClientError::NoTargets);
121 }
122 let attempts = self.retry.max_attempts.max(1);
123 let mut idx = self.cursor.fetch_add(1, Ordering::Relaxed) % n;
126 let mut last = ClientError::NoLeader { attempts };
127
128 for attempt in 0..attempts {
129 let target = self.targets[idx % n];
130 let send = send_client_request(&*self.transport, target, &request);
131 match tokio::time::timeout(self.retry.attempt_timeout, send).await {
132 Ok(Ok(ClientResponse::Ok(bytes))) => return Ok(bytes),
133 Ok(Ok(ClientResponse::NotLeader { leader })) => {
134 last = ClientError::NoLeader { attempts };
135 idx = leader
138 .and_then(|l| self.targets.iter().position(|t| *t == l))
139 .unwrap_or(idx + 1);
140 }
141 Ok(Ok(ClientResponse::Error(msg))) => {
142 last = ClientError::Server(msg);
146 idx += 1;
147 }
148 Ok(Ok(ClientResponse::ReadIndexConfirmed { .. })) => {
149 last =
150 ClientError::Server("unexpected ReadIndexConfirmed on client wire".into());
151 idx += 1;
152 }
153 Ok(Err(e)) => {
154 last = ClientError::Unreachable {
155 attempts,
156 last: e.to_string(),
157 };
158 idx += 1;
159 }
160 Err(_elapsed) => {
161 last = ClientError::Timeout { attempts };
162 idx += 1;
163 }
164 }
165 if attempt + 1 < attempts {
166 tokio::time::sleep(self.retry.backoff).await;
167 }
168 }
169 Err(last)
170 }
171}
172
173impl KeyedClient for RemoteClient {
174 fn propose_keyed(
175 &self,
176 key: Vec<u8>,
177 payload: Vec<u8>,
178 ) -> impl Future<Output = Result<Vec<u8>, ClientError>> + Send {
179 self.call(ClientRequest::ProposeKeyed {
180 key,
181 command: payload,
182 })
183 }
184
185 fn query_keyed(
186 &self,
187 key: Vec<u8>,
188 payload: Vec<u8>,
189 ) -> impl Future<Output = Result<Vec<u8>, ClientError>> + Send {
190 self.call(ClientRequest::QueryKeyed {
191 key,
192 query: payload,
193 })
194 }
195}
196
197impl crate::two_phase::TwoPhaseClient for RemoteClient {
198 fn prepare_keyed(
199 &self,
200 tx_id: Vec<u8>,
201 key: Vec<u8>,
202 command: Vec<u8>,
203 ) -> impl Future<Output = Result<Vec<u8>, ClientError>> + Send {
204 self.call(ClientRequest::TwoPhasePrepare {
205 tx_id,
206 key,
207 command,
208 })
209 }
210
211 fn commit_keyed(
212 &self,
213 tx_id: Vec<u8>,
214 key: Vec<u8>,
215 ) -> impl Future<Output = Result<Vec<u8>, ClientError>> + Send {
216 self.call(ClientRequest::TwoPhaseCommit { tx_id, key })
217 }
218
219 fn abort_keyed(
220 &self,
221 tx_id: Vec<u8>,
222 key: Vec<u8>,
223 ) -> impl Future<Output = Result<Vec<u8>, ClientError>> + Send {
224 self.call(ClientRequest::TwoPhaseAbort { tx_id, key })
225 }
226}
227
228impl Client for RemoteClient {
229 fn propose(
230 &self,
231 payload: Vec<u8>,
232 ) -> impl Future<Output = Result<Vec<u8>, ClientError>> + Send {
233 self.call(ClientRequest::Propose(payload))
234 }
235
236 fn query(&self, payload: Vec<u8>) -> impl Future<Output = Result<Vec<u8>, ClientError>> + Send {
237 self.call(ClientRequest::Query(payload))
238 }
239}