1use std::net::SocketAddr;
33
34use memcache_proto::ResponseBytes as McResponseBytes;
35use ringline::ConnCtx;
36
37use crate::{
38 Client, Error, GetValue, Value, check_error_bytes, encode_add, encode_request, encode_set,
39};
40use memcache_proto::Request as McRequest;
41
42pub struct ShardedConfig {
44 pub servers: Vec<SocketAddr>,
46 pub pool_size: usize,
48 pub connect_timeout_ms: u64,
50 pub tls_server_name: Option<String>,
52}
53
54enum ShardConn {
55 Connected(ConnCtx),
56 Disconnected,
57}
58
59struct Shard {
60 addr: SocketAddr,
61 conns: Vec<ShardConn>,
62 next: usize,
63}
64
65pub struct ShardedClient {
71 shards: Vec<Shard>,
72 ring: ketama::Ring,
73 connect_timeout_ms: u64,
74 tls_server_name: Option<String>,
75}
76
77impl ShardedClient {
78 pub fn new(config: ShardedConfig) -> Self {
80 let pool_size = config.pool_size.max(1);
81
82 let server_ids: Vec<String> = config.servers.iter().map(|a| a.to_string()).collect();
83 let ring = ketama::Ring::build(&server_ids.iter().map(|s| s.as_str()).collect::<Vec<_>>());
84
85 let shards = config
86 .servers
87 .iter()
88 .map(|&addr| {
89 let mut conns = Vec::with_capacity(pool_size);
90 for _ in 0..pool_size {
91 conns.push(ShardConn::Disconnected);
92 }
93 Shard {
94 addr,
95 conns,
96 next: 0,
97 }
98 })
99 .collect();
100
101 Self {
102 shards,
103 ring,
104 connect_timeout_ms: config.connect_timeout_ms,
105 tls_server_name: config.tls_server_name,
106 }
107 }
108
109 pub async fn connect_all(&mut self) -> Result<(), Error> {
111 let opts = self.connect_opts();
112 for shard in &mut self.shards {
113 for slot in &mut shard.conns {
114 let conn = do_connect(shard.addr, &opts).await?;
115 *slot = ShardConn::Connected(conn);
116 }
117 }
118 Ok(())
119 }
120
121 pub fn close_all(&mut self) {
123 for shard in &mut self.shards {
124 for slot in &mut shard.conns {
125 if let ShardConn::Connected(c) = slot {
126 c.close();
127 }
128 *slot = ShardConn::Disconnected;
129 }
130 }
131 }
132
133 pub fn shard_count(&self) -> usize {
135 self.shards.len()
136 }
137
138 fn connect_opts(&self) -> ConnectOpts {
139 ConnectOpts {
140 connect_timeout_ms: self.connect_timeout_ms,
141 tls_server_name: self.tls_server_name.clone(),
142 }
143 }
144
145 pub async fn shard_client(&mut self, index: usize) -> Result<Client, Error> {
147 let opts = self.connect_opts();
148 let shard = &mut self.shards[index];
149 let conn = get_conn(shard, &opts).await?;
150 Ok(Client::new(conn))
151 }
152
153 async fn route_command(
157 &mut self,
158 key: &[u8],
159 encoded: &[u8],
160 ) -> Result<McResponseBytes, Error> {
161 let opts = self.connect_opts();
162 let shard_idx = self.ring.route(key);
163 let shard = &mut self.shards[shard_idx];
164 let size = shard.conns.len();
165
166 for attempt in 0..size {
167 let idx = (shard.next + attempt) % size;
168 let conn = match &shard.conns[idx] {
169 ShardConn::Connected(c) => *c,
170 ShardConn::Disconnected => match do_connect(shard.addr, &opts).await {
171 Ok(c) => {
172 shard.conns[idx] = ShardConn::Connected(c);
173 c
174 }
175 Err(_) => continue,
176 },
177 };
178
179 if let Err(e) = conn.send(encoded) {
180 shard.conns[idx] = ShardConn::Disconnected;
181 conn.close();
182 return Err(Error::Io(e));
183 }
184 match Client::new(conn).read_response().await {
185 Ok(response) => {
186 shard.next = (idx + 1) % size;
187 check_error_bytes(&response)?;
188 return Ok(response);
189 }
190 Err(Error::ConnectionClosed) => {
191 shard.conns[idx] = ShardConn::Disconnected;
192 continue;
193 }
194 Err(e) => return Err(e),
195 }
196 }
197
198 Err(Error::AllConnectionsFailed)
199 }
200
201 fn require_same_shard(&self, keys: &[&[u8]]) -> Result<usize, Error> {
203 let first = self.ring.route(keys[0]);
204 for key in &keys[1..] {
205 if self.ring.route(key) != first {
206 return Err(Error::Memcache(
207 "keys in request don't route to the same shard".into(),
208 ));
209 }
210 }
211 Ok(first)
212 }
213
214 pub async fn get(&mut self, key: impl AsRef<[u8]>) -> Result<Option<Value>, Error> {
218 let key = key.as_ref();
219 let encoded = encode_request(&memcache_proto::Request::get(key));
220 let response = self.route_command(key, &encoded).await?;
221 match response {
222 McResponseBytes::Values(mut values) => {
223 if values.is_empty() {
224 Ok(None)
225 } else {
226 let v = values.swap_remove(0);
227 Ok(Some(Value {
228 data: v.data,
229 flags: v.flags,
230 }))
231 }
232 }
233 _ => Err(Error::UnexpectedResponse),
234 }
235 }
236
237 pub async fn gets(&mut self, keys: &[&[u8]]) -> Result<Vec<GetValue>, Error> {
240 if keys.is_empty() {
241 return Ok(Vec::new());
242 }
243 self.require_same_shard(keys)?;
244 let encoded = encode_request(&McRequest::gets(keys));
245 let response = self.route_command(keys[0], &encoded).await?;
246 match response {
247 McResponseBytes::Values(values) => Ok(values
248 .into_iter()
249 .map(|v| GetValue {
250 key: v.key,
251 data: v.data,
252 flags: v.flags,
253 cas: v.cas,
254 })
255 .collect()),
256 _ => Err(Error::UnexpectedResponse),
257 }
258 }
259
260 pub async fn set(
262 &mut self,
263 key: impl AsRef<[u8]>,
264 value: impl AsRef<[u8]>,
265 ) -> Result<(), Error> {
266 self.set_with_options(key, value, 0, 0).await
267 }
268
269 pub async fn set_with_options(
271 &mut self,
272 key: impl AsRef<[u8]>,
273 value: impl AsRef<[u8]>,
274 flags: u32,
275 exptime: u32,
276 ) -> Result<(), Error> {
277 let key = key.as_ref();
278 let value = value.as_ref();
279 let encoded = encode_set(key, value, flags, exptime);
280 let response = self.route_command(key, &encoded).await?;
281 match response {
282 McResponseBytes::Stored => Ok(()),
283 _ => Err(Error::UnexpectedResponse),
284 }
285 }
286
287 pub async fn add(
290 &mut self,
291 key: impl AsRef<[u8]>,
292 value: impl AsRef<[u8]>,
293 ) -> Result<bool, Error> {
294 let key = key.as_ref();
295 let value = value.as_ref();
296 let encoded = encode_add(key, value);
297 let response = self.route_command(key, &encoded).await?;
298 match response {
299 McResponseBytes::Stored => Ok(true),
300 McResponseBytes::NotStored => Ok(false),
301 _ => Err(Error::UnexpectedResponse),
302 }
303 }
304
305 pub async fn replace(
308 &mut self,
309 key: impl AsRef<[u8]>,
310 value: impl AsRef<[u8]>,
311 ) -> Result<bool, Error> {
312 let key = key.as_ref();
313 let value = value.as_ref();
314 let encoded = encode_request(&McRequest::Replace {
315 key,
316 value,
317 flags: 0,
318 exptime: 0,
319 });
320 let response = self.route_command(key, &encoded).await?;
321 match response {
322 McResponseBytes::Stored => Ok(true),
323 McResponseBytes::NotStored => Ok(false),
324 _ => Err(Error::UnexpectedResponse),
325 }
326 }
327
328 pub async fn incr(&mut self, key: impl AsRef<[u8]>, delta: u64) -> Result<Option<u64>, Error> {
331 let key = key.as_ref();
332 let encoded = encode_request(&McRequest::incr(key, delta));
333 let response = self.route_command(key, &encoded).await?;
334 match response {
335 McResponseBytes::Numeric(val) => Ok(Some(val)),
336 McResponseBytes::NotFound => Ok(None),
337 _ => Err(Error::UnexpectedResponse),
338 }
339 }
340
341 pub async fn decr(&mut self, key: impl AsRef<[u8]>, delta: u64) -> Result<Option<u64>, Error> {
344 let key = key.as_ref();
345 let encoded = encode_request(&McRequest::decr(key, delta));
346 let response = self.route_command(key, &encoded).await?;
347 match response {
348 McResponseBytes::Numeric(val) => Ok(Some(val)),
349 McResponseBytes::NotFound => Ok(None),
350 _ => Err(Error::UnexpectedResponse),
351 }
352 }
353
354 pub async fn append(
357 &mut self,
358 key: impl AsRef<[u8]>,
359 value: impl AsRef<[u8]>,
360 ) -> Result<bool, Error> {
361 let key = key.as_ref();
362 let value = value.as_ref();
363 let encoded = encode_request(&McRequest::append(key, value));
364 let response = self.route_command(key, &encoded).await?;
365 match response {
366 McResponseBytes::Stored => Ok(true),
367 McResponseBytes::NotStored => Ok(false),
368 _ => Err(Error::UnexpectedResponse),
369 }
370 }
371
372 pub async fn prepend(
375 &mut self,
376 key: impl AsRef<[u8]>,
377 value: impl AsRef<[u8]>,
378 ) -> Result<bool, Error> {
379 let key = key.as_ref();
380 let value = value.as_ref();
381 let encoded = encode_request(&McRequest::prepend(key, value));
382 let response = self.route_command(key, &encoded).await?;
383 match response {
384 McResponseBytes::Stored => Ok(true),
385 McResponseBytes::NotStored => Ok(false),
386 _ => Err(Error::UnexpectedResponse),
387 }
388 }
389
390 pub async fn cas(
394 &mut self,
395 key: impl AsRef<[u8]>,
396 value: impl AsRef<[u8]>,
397 cas_unique: u64,
398 ) -> Result<bool, Error> {
399 let key = key.as_ref();
400 let value = value.as_ref();
401 let encoded = encode_request(&McRequest::cas(key, value, cas_unique));
402 let response = self.route_command(key, &encoded).await?;
403 match response {
404 McResponseBytes::Stored => Ok(true),
405 McResponseBytes::Exists => Ok(false),
406 McResponseBytes::NotFound => Err(Error::Memcache("NOT_FOUND".into())),
407 _ => Err(Error::UnexpectedResponse),
408 }
409 }
410
411 pub async fn delete(&mut self, key: impl AsRef<[u8]>) -> Result<bool, Error> {
413 let key = key.as_ref();
414 let encoded = encode_request(&McRequest::delete(key));
415 let response = self.route_command(key, &encoded).await?;
416 match response {
417 McResponseBytes::Deleted => Ok(true),
418 McResponseBytes::NotFound => Ok(false),
419 _ => Err(Error::UnexpectedResponse),
420 }
421 }
422
423 pub async fn flush_all(&mut self) -> Result<(), Error> {
425 let opts = self.connect_opts();
426 for shard in &mut self.shards {
427 let conn = get_conn(shard, &opts).await?;
428 Client::new(conn).flush_all().await?;
429 }
430 Ok(())
431 }
432
433 pub async fn version(&mut self) -> Result<String, Error> {
435 let opts = self.connect_opts();
436 for shard in &mut self.shards {
437 if let Ok(conn) = get_conn(shard, &opts).await {
438 return Client::new(conn).version().await;
439 }
440 }
441 Err(Error::AllConnectionsFailed)
442 }
443}
444
445#[derive(Clone)]
447struct ConnectOpts {
448 connect_timeout_ms: u64,
449 tls_server_name: Option<String>,
450}
451
452async fn get_conn(shard: &mut Shard, opts: &ConnectOpts) -> Result<ConnCtx, Error> {
454 let size = shard.conns.len();
455 for _ in 0..size {
456 let idx = shard.next;
457 shard.next = (shard.next + 1) % size;
458
459 match &shard.conns[idx] {
460 ShardConn::Connected(c) => return Ok(*c),
461 ShardConn::Disconnected => {
462 if let Ok(conn) = do_connect(shard.addr, opts).await {
463 shard.conns[idx] = ShardConn::Connected(conn);
464 return Ok(conn);
465 }
466 }
467 }
468 }
469 Err(Error::AllConnectionsFailed)
470}
471
472async fn do_connect(addr: SocketAddr, opts: &ConnectOpts) -> Result<ConnCtx, Error> {
473 let conn = if let Some(ref sni) = opts.tls_server_name {
474 let fut = if opts.connect_timeout_ms > 0 {
475 ringline::connect_tls_with_timeout(addr, sni, opts.connect_timeout_ms)?
476 } else {
477 ringline::connect_tls(addr, sni)?
478 };
479 fut.await?
480 } else {
481 let fut = if opts.connect_timeout_ms > 0 {
482 ringline::connect_with_timeout(addr, opts.connect_timeout_ms)?
483 } else {
484 ringline::connect(addr)?
485 };
486 fut.await?
487 };
488
489 Ok(conn)
490}
491
492#[cfg(test)]
493mod tests {
494 use super::*;
495
496 #[test]
497 fn test_single_server_always_routes_to_zero() {
498 let config = ShardedConfig {
499 servers: vec!["127.0.0.1:11211".parse().unwrap()],
500 pool_size: 1,
501 connect_timeout_ms: 0,
502 tls_server_name: None,
503 };
504 let client = ShardedClient::new(config);
505 assert_eq!(client.ring.route(b"any-key"), 0);
506 assert_eq!(client.ring.route(b"another-key"), 0);
507 assert_eq!(client.ring.route(b""), 0);
508 }
509
510 #[test]
511 fn test_deterministic_routing() {
512 let config = ShardedConfig {
513 servers: vec![
514 "127.0.0.1:11211".parse().unwrap(),
515 "127.0.0.1:11212".parse().unwrap(),
516 "127.0.0.1:11213".parse().unwrap(),
517 ],
518 pool_size: 1,
519 connect_timeout_ms: 0,
520 tls_server_name: None,
521 };
522 let client = ShardedClient::new(config);
523
524 let a = client.ring.route(b"test-key");
525 let b = client.ring.route(b"test-key");
526 assert_eq!(a, b);
527
528 let c = client.ring.route(b"other-key");
529 let d = client.ring.route(b"other-key");
530 assert_eq!(c, d);
531 }
532
533 #[test]
534 fn test_config_defaults() {
535 let config = ShardedConfig {
536 servers: vec![
537 "127.0.0.1:11211".parse().unwrap(),
538 "127.0.0.1:11212".parse().unwrap(),
539 ],
540 pool_size: 4,
541 connect_timeout_ms: 500,
542 tls_server_name: None,
543 };
544 let client = ShardedClient::new(config);
545 assert_eq!(client.shard_count(), 2);
546 assert_eq!(client.ring.node_count(), 2);
547 assert_eq!(client.shards[0].conns.len(), 4);
548 assert_eq!(client.shards[1].conns.len(), 4);
549 }
550
551 #[test]
552 fn test_pool_size_minimum() {
553 let config = ShardedConfig {
554 servers: vec!["127.0.0.1:11211".parse().unwrap()],
555 pool_size: 0,
556 connect_timeout_ms: 0,
557 tls_server_name: None,
558 };
559 let client = ShardedClient::new(config);
560 assert_eq!(client.shards[0].conns.len(), 1);
561 }
562
563 #[test]
564 fn test_require_same_shard_matching() {
565 let config = ShardedConfig {
566 servers: vec![
567 "127.0.0.1:11211".parse().unwrap(),
568 "127.0.0.1:11212".parse().unwrap(),
569 ],
570 pool_size: 1,
571 connect_timeout_ms: 0,
572 tls_server_name: None,
573 };
574 let client = ShardedClient::new(config);
575 let keys: &[&[u8]] = &[b"same-key", b"same-key"];
576 assert!(client.require_same_shard(keys).is_ok());
577 }
578
579 #[test]
580 fn test_require_same_shard_single_key() {
581 let config = ShardedConfig {
582 servers: vec![
583 "127.0.0.1:11211".parse().unwrap(),
584 "127.0.0.1:11212".parse().unwrap(),
585 ],
586 pool_size: 1,
587 connect_timeout_ms: 0,
588 tls_server_name: None,
589 };
590 let client = ShardedClient::new(config);
591 let keys: &[&[u8]] = &[b"anykey"];
592 assert!(client.require_same_shard(keys).is_ok());
593 }
594}