Skip to main content

ringline_memcache/
lib.rs

1//! ringline-native Memcache client for use inside the ringline async runtime.
2//!
3//! This client wraps a [`ringline::ConnCtx`] and provides typed Memcache command
4//! methods that use `with_bytes()` + `ResponseBytes::parse()` for zero-copy
5//! incremental parsing. It is designed for single-threaded, single-connection
6//! use within ringline's `AsyncEventHandler::on_start()` or connection tasks.
7//!
8//! All key and value parameters accept `impl AsRef<[u8]>`, so you can pass
9//! `&str`, `String`, `&[u8]`, `Vec<u8>`, `Bytes`, etc.
10//!
11//! # Example
12//!
13//! ```no_run
14//! use ringline::ConnCtx;
15//! use ringline_memcache::Client;
16//!
17//! async fn example(conn: ConnCtx) -> Result<(), ringline_memcache::Error> {
18//!     let mut client = Client::new(conn);
19//!     client.set("hello", "world").await?;
20//!     let val = client.get("hello").await?;
21//!     assert_eq!(val.unwrap().data.as_ref(), b"world");
22//!     Ok(())
23//! }
24//! ```
25//!
26//! # Copy Semantics
27//!
28//! | Path | Copies | Mechanism |
29//! |------|--------|-----------|
30//! | **Recv (values)** | **0** | `with_bytes()` + `ResponseBytes::parse()`. Keys and values are `Bytes::slice()` references into the accumulator -- zero allocation, O(1) refcount. |
31//! | **Send (commands)** | 1 | `encode_request()` serializes into `Vec<u8>`, then `conn.send()` copies into the send pool. |
32//! | **Send (SET value, guard)** | 0 (value) | [`Client::set_with_guard`]: prefix+suffix copied to pool, value stays in-place via `SendGuard`. |
33//!
34//! TLS connections add encryption copies on the send path regardless of
35//! `SendGuard` usage.
36
37pub mod pool;
38pub mod sharded;
39pub use pool::{Pool, PoolConfig};
40pub use sharded::{ShardedClient, ShardedConfig};
41
42use std::collections::VecDeque;
43use std::io;
44use std::time::Instant;
45
46use bytes::Bytes;
47use memcache_proto::{Request as McRequest, ResponseBytes as McResponseBytes};
48use ringline::{ConnCtx, GuardBox, ParseResult, SendGuard};
49
50/// Callback type invoked after each command completes.
51type ResultCallback = Box<dyn Fn(&CommandResult)>;
52
53// -- Error -------------------------------------------------------------------
54
55/// Errors returned by the ringline Memcache client.
56#[derive(Debug, thiserror::Error)]
57pub enum Error {
58    /// The connection was closed before a response was received.
59    #[error("connection closed")]
60    ConnectionClosed,
61
62    /// The server returned an error response (ERROR, CLIENT_ERROR, SERVER_ERROR).
63    #[error("memcache error: {0}")]
64    Memcache(String),
65
66    /// The response type did not match the expected type for the command.
67    #[error("unexpected response")]
68    UnexpectedResponse,
69
70    /// Memcache protocol parse error.
71    #[error("protocol error: {0}")]
72    Protocol(#[from] memcache_proto::ParseError),
73
74    /// I/O error during send.
75    #[error("io error: {0}")]
76    Io(#[from] io::Error),
77
78    /// All connections in the pool are down and reconnection failed.
79    #[error("all connections failed")]
80    AllConnectionsFailed,
81
82    /// `recv()` called with no pending fire operations.
83    #[error("no pending operations")]
84    NoPending,
85}
86
87// -- Value types -------------------------------------------------------------
88
89/// A value returned from a single-key GET command.
90#[derive(Debug, Clone)]
91pub struct Value {
92    /// The cached data.
93    pub data: Bytes,
94    /// Flags stored with the item.
95    pub flags: u32,
96}
97
98/// A value returned from a multi-key GET command, including the key.
99#[derive(Debug, Clone)]
100pub struct GetValue {
101    /// The key for this value.
102    pub key: Bytes,
103    /// The cached data.
104    pub data: Bytes,
105    /// Flags stored with the item.
106    pub flags: u32,
107    /// CAS unique token (present when the server returns it via `gets`).
108    pub cas: Option<u64>,
109}
110
111// ── Command types ───────────────────────────────────────────────────────
112
113/// The type of Memcache command that completed.
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum CommandType {
116    Get,
117    Set,
118    Delete,
119    Other,
120}
121
122/// Result metadata for a completed command, passed to the `on_result` callback.
123#[derive(Debug, Clone)]
124pub struct CommandResult {
125    /// The command type.
126    pub command: CommandType,
127    /// Latency in nanoseconds (send → response parsed).
128    pub latency_ns: u64,
129    /// For GET: `Some(true)` = hit, `Some(false)` = miss. `None` for others.
130    pub hit: Option<bool>,
131    /// Whether the command succeeded (no error response).
132    pub success: bool,
133    /// Time-to-first-byte in nanoseconds (not available in sequential mode).
134    pub ttfb_ns: Option<u64>,
135}
136
137// ── ClientMetrics ───────────────────────────────────────────────────────
138
139/// Built-in histogram-based metrics, available when the `metrics` feature is
140/// enabled. Not registered globally — the caller decides how to expose them.
141#[cfg(feature = "metrics")]
142pub struct ClientMetrics {
143    /// Overall request latency histogram.
144    pub latency: histogram::Histogram,
145    /// GET latency histogram.
146    pub get_latency: histogram::Histogram,
147    /// SET latency histogram.
148    pub set_latency: histogram::Histogram,
149    /// DEL latency histogram.
150    pub del_latency: histogram::Histogram,
151    /// Total requests completed.
152    pub requests: u64,
153    /// Total errors.
154    pub errors: u64,
155    /// Total GET hits.
156    pub hits: u64,
157    /// Total GET misses.
158    pub misses: u64,
159}
160
161#[cfg(feature = "metrics")]
162impl ClientMetrics {
163    fn new() -> Self {
164        Self {
165            latency: histogram::Histogram::new(7, 64).unwrap(),
166            get_latency: histogram::Histogram::new(7, 64).unwrap(),
167            set_latency: histogram::Histogram::new(7, 64).unwrap(),
168            del_latency: histogram::Histogram::new(7, 64).unwrap(),
169            requests: 0,
170            errors: 0,
171            hits: 0,
172            misses: 0,
173        }
174    }
175
176    fn record(&mut self, result: &CommandResult) {
177        self.requests += 1;
178        let _ = self.latency.increment(result.latency_ns);
179
180        if !result.success {
181            self.errors += 1;
182        }
183
184        match result.command {
185            CommandType::Get => {
186                let _ = self.get_latency.increment(result.latency_ns);
187                match result.hit {
188                    Some(true) => self.hits += 1,
189                    Some(false) => self.misses += 1,
190                    None => {}
191                }
192            }
193            CommandType::Set => {
194                let _ = self.set_latency.increment(result.latency_ns);
195            }
196            CommandType::Delete => {
197                let _ = self.del_latency.increment(result.latency_ns);
198            }
199            _ => {}
200        }
201    }
202}
203
204// ── Pending operation state ─────────────────────────────────────────────
205
206enum PendingOpKind {
207    Get,
208    Set,
209    Delete,
210}
211
212struct PendingOp {
213    kind: PendingOpKind,
214    send_ts: u64,
215    start: Option<Instant>,
216    user_data: u64,
217}
218
219/// A completed fire/recv operation with its result.
220pub enum CompletedOp {
221    /// GET completed.
222    Get {
223        result: Result<Option<Value>, Error>,
224        user_data: u64,
225    },
226    /// SET completed.
227    Set {
228        result: Result<(), Error>,
229        user_data: u64,
230    },
231    /// DELETE completed.
232    Delete {
233        result: Result<bool, Error>,
234        user_data: u64,
235    },
236}
237
238// ── ClientBuilder ───────────────────────────────────────────────────────
239
240/// Builder for creating a [`Client`] with per-request callbacks and metrics.
241pub struct ClientBuilder {
242    conn: ConnCtx,
243    on_result: Option<ResultCallback>,
244    #[cfg(feature = "timestamps")]
245    use_kernel_ts: bool,
246    #[cfg(feature = "metrics")]
247    with_metrics: bool,
248}
249
250impl ClientBuilder {
251    pub(crate) fn new(conn: ConnCtx) -> Self {
252        Self {
253            conn,
254            on_result: None,
255            #[cfg(feature = "timestamps")]
256            use_kernel_ts: false,
257            #[cfg(feature = "metrics")]
258            with_metrics: false,
259        }
260    }
261
262    /// Register a callback invoked after each command completes.
263    pub fn on_result<F: Fn(&CommandResult) + 'static>(mut self, f: F) -> Self {
264        self.on_result = Some(Box::new(f));
265        self
266    }
267
268    /// Enable kernel SO_TIMESTAMPING for latency measurement (requires `timestamps` feature).
269    #[cfg(feature = "timestamps")]
270    pub fn kernel_timestamps(mut self, enabled: bool) -> Self {
271        self.use_kernel_ts = enabled;
272        self
273    }
274
275    /// Enable built-in histogram tracking (requires `metrics` feature).
276    #[cfg(feature = "metrics")]
277    pub fn with_metrics(mut self) -> Self {
278        self.with_metrics = true;
279        self
280    }
281
282    /// Build the client.
283    pub fn build(self) -> Client {
284        Client {
285            conn: self.conn,
286            on_result: self.on_result,
287            pending: VecDeque::new(),
288            #[cfg(feature = "timestamps")]
289            use_kernel_ts: self.use_kernel_ts,
290            #[cfg(feature = "metrics")]
291            metrics: if self.with_metrics {
292                Some(ClientMetrics::new())
293            } else {
294                None
295            },
296        }
297    }
298}
299
300// -- Client ------------------------------------------------------------------
301
302/// A ringline-native Memcache client wrapping a single connection.
303///
304/// `Client::new(conn)` creates a zero-overhead client with no callbacks or
305/// metrics. Use `Client::builder(conn)` to configure per-request callbacks,
306/// kernel timestamps, and built-in histogram tracking.
307pub struct Client {
308    conn: ConnCtx,
309    on_result: Option<ResultCallback>,
310    pending: VecDeque<PendingOp>,
311    #[cfg(feature = "timestamps")]
312    use_kernel_ts: bool,
313    #[cfg(feature = "metrics")]
314    metrics: Option<ClientMetrics>,
315}
316
317impl Client {
318    /// Create a new client wrapping an established connection.
319    ///
320    /// No callbacks, no metrics, no kernel timestamps — zero overhead.
321    pub fn new(conn: ConnCtx) -> Self {
322        Self {
323            conn,
324            on_result: None,
325            pending: VecDeque::new(),
326            #[cfg(feature = "timestamps")]
327            use_kernel_ts: false,
328            #[cfg(feature = "metrics")]
329            metrics: None,
330        }
331    }
332
333    /// Create a builder for a client with per-request callbacks.
334    pub fn builder(conn: ConnCtx) -> ClientBuilder {
335        ClientBuilder::new(conn)
336    }
337
338    /// Returns the underlying connection context.
339    pub fn conn(&self) -> ConnCtx {
340        self.conn
341    }
342
343    /// Returns a reference to the built-in metrics, if enabled.
344    #[cfg(feature = "metrics")]
345    pub fn metrics(&self) -> Option<&ClientMetrics> {
346        self.metrics.as_ref()
347    }
348
349    /// Returns a mutable reference to the built-in metrics, if enabled.
350    #[cfg(feature = "metrics")]
351    pub fn metrics_mut(&mut self) -> Option<&mut ClientMetrics> {
352        self.metrics.as_mut()
353    }
354
355    // ── Timing helpers (private) ────────────────────────────────────────
356
357    #[inline]
358    fn is_instrumented(&self) -> bool {
359        if self.on_result.is_some() {
360            return true;
361        }
362        #[cfg(feature = "metrics")]
363        if self.metrics.is_some() {
364            return true;
365        }
366        false
367    }
368
369    #[cfg(feature = "timestamps")]
370    #[inline]
371    fn send_timestamp(&self) -> u64 {
372        if self.use_kernel_ts {
373            now_realtime_ns()
374        } else {
375            0
376        }
377    }
378
379    #[cfg(not(feature = "timestamps"))]
380    #[inline]
381    fn send_timestamp(&self) -> u64 {
382        0
383    }
384
385    #[cfg(feature = "timestamps")]
386    #[inline]
387    fn finish_timing(&self, send_ts: u64, start: Instant) -> u64 {
388        if self.use_kernel_ts {
389            let recv_ts = self.conn.recv_timestamp();
390            if recv_ts > 0 && recv_ts > send_ts {
391                return recv_ts - send_ts;
392            }
393        }
394        start.elapsed().as_nanos() as u64
395    }
396
397    #[cfg(not(feature = "timestamps"))]
398    #[inline]
399    fn finish_timing(&self, _send_ts: u64, start: Instant) -> u64 {
400        start.elapsed().as_nanos() as u64
401    }
402
403    fn record(&mut self, result: &CommandResult) {
404        if let Some(ref cb) = self.on_result {
405            cb(result);
406        }
407        #[cfg(feature = "metrics")]
408        if let Some(ref mut m) = self.metrics {
409            m.record(result);
410        }
411    }
412
413    // ── Fire/recv pipelining API ─────────────────────────────────────────
414
415    #[inline]
416    fn timing_start(&self) -> (u64, Option<Instant>) {
417        if self.is_instrumented() {
418            (self.send_timestamp(), Some(Instant::now()))
419        } else {
420            (0, None)
421        }
422    }
423
424    /// Number of in-flight requests.
425    pub fn pending_count(&self) -> usize {
426        self.pending.len()
427    }
428
429    /// Fire a GET request without waiting for the response.
430    pub fn fire_get(&mut self, key: &[u8], user_data: u64) -> Result<(), Error> {
431        let encoded = encode_request(&McRequest::get(key));
432        self.conn.send_nowait(&encoded)?;
433        let (send_ts, start) = self.timing_start();
434        self.pending.push_back(PendingOp {
435            kind: PendingOpKind::Get,
436            send_ts,
437            start,
438            user_data,
439        });
440        Ok(())
441    }
442
443    /// Fire a SET request (with copy) without waiting for the response.
444    pub fn fire_set(
445        &mut self,
446        key: &[u8],
447        value: &[u8],
448        flags: u32,
449        exptime: u32,
450        user_data: u64,
451    ) -> Result<(), Error> {
452        let encoded = encode_set(key, value, flags, exptime);
453        self.conn.send_nowait(&encoded)?;
454        let (send_ts, start) = self.timing_start();
455        self.pending.push_back(PendingOp {
456            kind: PendingOpKind::Set,
457            send_ts,
458            start,
459            user_data,
460        });
461        Ok(())
462    }
463
464    /// Fire a SET request with zero-copy value via SendGuard.
465    pub fn fire_set_with_guard<G: SendGuard>(
466        &mut self,
467        key: &[u8],
468        guard: G,
469        flags: u32,
470        exptime: u32,
471        user_data: u64,
472    ) -> Result<(), Error> {
473        let (_, value_len) = guard.as_ptr_len();
474        let prefix = encode_set_guard_prefix(key, value_len as usize, flags, exptime);
475        self.conn.send_parts().build(move |b| {
476            b.copy(&prefix)
477                .guard(GuardBox::new(guard))
478                .copy(b"\r\n")
479                .submit()
480        })?;
481        let (send_ts, start) = self.timing_start();
482        self.pending.push_back(PendingOp {
483            kind: PendingOpKind::Set,
484            send_ts,
485            start,
486            user_data,
487        });
488        Ok(())
489    }
490
491    /// Fire a DELETE request without waiting for the response.
492    pub fn fire_delete(&mut self, key: &[u8], user_data: u64) -> Result<(), Error> {
493        let encoded = encode_request(&McRequest::delete(key));
494        self.conn.send_nowait(&encoded)?;
495        let (send_ts, start) = self.timing_start();
496        self.pending.push_back(PendingOp {
497            kind: PendingOpKind::Delete,
498            send_ts,
499            start,
500            user_data,
501        });
502        Ok(())
503    }
504
505    /// Receive the next completed operation from the pipeline.
506    ///
507    /// Returns `Err(Error::NoPending)` if there are no in-flight requests.
508    pub async fn recv(&mut self) -> Result<CompletedOp, Error> {
509        let pending = self.pending.pop_front().ok_or(Error::NoPending)?;
510
511        let response = match self.read_response().await {
512            Ok(v) => v,
513            Err(e) => {
514                // Connection is broken — clear remaining pending ops so
515                // subsequent recv() calls return NoPending instead of
516                // reading stale/misaligned responses.
517                self.pending.clear();
518                return Err(e);
519            }
520        };
521        let latency_ns = match pending.start {
522            Some(start) => self.finish_timing(pending.send_ts, start),
523            None => 0,
524        };
525
526        let op = match pending.kind {
527            PendingOpKind::Get => {
528                // Check for error responses first
529                let result = match check_error_bytes(&response) {
530                    Err(e) => Err(e),
531                    Ok(()) => match response {
532                        McResponseBytes::Values(mut values) => {
533                            if values.is_empty() {
534                                Ok(None)
535                            } else {
536                                let v = values.swap_remove(0);
537                                Ok(Some(Value {
538                                    data: v.data,
539                                    flags: v.flags,
540                                }))
541                            }
542                        }
543                        _ => Err(Error::UnexpectedResponse),
544                    },
545                };
546                let (success, hit) = match &result {
547                    Ok(Some(_)) => (true, Some(true)),
548                    Ok(None) => (true, Some(false)),
549                    Err(_) => (false, None),
550                };
551                self.record(&CommandResult {
552                    command: CommandType::Get,
553                    latency_ns,
554                    hit,
555                    success,
556                    ttfb_ns: None,
557                });
558                CompletedOp::Get {
559                    result,
560                    user_data: pending.user_data,
561                }
562            }
563            PendingOpKind::Set => {
564                let result = match check_error_bytes(&response) {
565                    Err(e) => Err(e),
566                    Ok(()) => match response {
567                        McResponseBytes::Stored => Ok(()),
568                        _ => Err(Error::UnexpectedResponse),
569                    },
570                };
571                self.record(&CommandResult {
572                    command: CommandType::Set,
573                    latency_ns,
574                    hit: None,
575                    success: result.is_ok(),
576                    ttfb_ns: None,
577                });
578                CompletedOp::Set {
579                    result,
580                    user_data: pending.user_data,
581                }
582            }
583            PendingOpKind::Delete => {
584                let result = match check_error_bytes(&response) {
585                    Err(e) => Err(e),
586                    Ok(()) => match response {
587                        McResponseBytes::Deleted => Ok(true),
588                        McResponseBytes::NotFound => Ok(false),
589                        _ => Err(Error::UnexpectedResponse),
590                    },
591                };
592                self.record(&CommandResult {
593                    command: CommandType::Delete,
594                    latency_ns,
595                    hit: None,
596                    success: result.is_ok(),
597                    ttfb_ns: None,
598                });
599                CompletedOp::Delete {
600                    result,
601                    user_data: pending.user_data,
602                }
603            }
604        };
605
606        Ok(op)
607    }
608
609    // ── Internal I/O (unchanged) ────────────────────────────────────────
610
611    /// Read and parse a single Memcache response from the connection.
612    ///
613    /// Uses zero-copy parsing via `with_bytes` + `ResponseBytes::parse`:
614    /// value data are `Bytes::slice()` references into the accumulator's
615    /// buffer rather than freshly allocated `Vec<u8>`.
616    pub(crate) async fn read_response(&self) -> Result<McResponseBytes, Error> {
617        let mut result: Option<Result<McResponseBytes, Error>> = None;
618        let n = self
619            .conn
620            .with_bytes(|bytes| {
621                let len = bytes.len();
622                match McResponseBytes::parse(bytes) {
623                    Ok((response, consumed)) => {
624                        result = Some(Ok(response));
625                        ParseResult::Consumed(consumed)
626                    }
627                    Err(e) if e.is_incomplete() => ParseResult::Consumed(0),
628                    Err(e) => {
629                        result = Some(Err(Error::Protocol(e)));
630                        ParseResult::Consumed(len)
631                    }
632                }
633            })
634            .await;
635        if n == 0 {
636            return result.unwrap_or(Err(Error::ConnectionClosed));
637        }
638        result.unwrap()
639    }
640
641    /// Send an encoded command and read the response, converting error
642    /// responses into `Error::Memcache`.
643    async fn execute(&self, encoded: &[u8]) -> Result<McResponseBytes, Error> {
644        self.conn.send(encoded)?;
645        let response = self.read_response().await?;
646        check_error_bytes(&response)?;
647        Ok(response)
648    }
649
650    // -- Commands (instrumented hot-path) ---------------------------------
651
652    /// Get the value of a key. Returns `None` on cache miss.
653    pub async fn get(&mut self, key: impl AsRef<[u8]>) -> Result<Option<Value>, Error> {
654        let key = key.as_ref();
655        let encoded = encode_request(&McRequest::get(key));
656
657        if !self.is_instrumented() {
658            let response = self.execute(&encoded).await?;
659            return match response {
660                McResponseBytes::Values(mut values) => {
661                    if values.is_empty() {
662                        Ok(None)
663                    } else {
664                        let v = values.swap_remove(0);
665                        Ok(Some(Value {
666                            data: v.data,
667                            flags: v.flags,
668                        }))
669                    }
670                }
671                _ => Err(Error::UnexpectedResponse),
672            };
673        }
674
675        let send_ts = self.send_timestamp();
676        let start = Instant::now();
677        let response = self.execute(&encoded).await;
678        let latency_ns = self.finish_timing(send_ts, start);
679
680        let result = match response {
681            Ok(McResponseBytes::Values(mut values)) => {
682                if values.is_empty() {
683                    Ok(None)
684                } else {
685                    let v = values.swap_remove(0);
686                    Ok(Some(Value {
687                        data: v.data,
688                        flags: v.flags,
689                    }))
690                }
691            }
692            Ok(_) => Err(Error::UnexpectedResponse),
693            Err(e) => Err(e),
694        };
695
696        let (success, hit) = match &result {
697            Ok(Some(_)) => (true, Some(true)),
698            Ok(None) => (true, Some(false)),
699            Err(_) => (false, None),
700        };
701        self.record(&CommandResult {
702            command: CommandType::Get,
703            latency_ns,
704            hit,
705            success,
706            ttfb_ns: None,
707        });
708        result
709    }
710
711    /// Get values for multiple keys. Returns only hits, each with its key and CAS token.
712    pub async fn gets(&mut self, keys: &[&[u8]]) -> Result<Vec<GetValue>, Error> {
713        if keys.is_empty() {
714            return Ok(Vec::new());
715        }
716        let encoded = encode_request(&McRequest::gets(keys));
717        let response = self.execute(&encoded).await?;
718        match response {
719            McResponseBytes::Values(values) => Ok(values
720                .into_iter()
721                .map(|v| GetValue {
722                    key: v.key,
723                    data: v.data,
724                    flags: v.flags,
725                    cas: v.cas,
726                })
727                .collect()),
728            _ => Err(Error::UnexpectedResponse),
729        }
730    }
731
732    /// Set a key-value pair with default flags (0) and no expiration.
733    pub async fn set(
734        &mut self,
735        key: impl AsRef<[u8]>,
736        value: impl AsRef<[u8]>,
737    ) -> Result<(), Error> {
738        self.set_with_options(key, value, 0, 0).await
739    }
740
741    /// Set a key-value pair with custom flags and expiration time.
742    pub async fn set_with_options(
743        &mut self,
744        key: impl AsRef<[u8]>,
745        value: impl AsRef<[u8]>,
746        flags: u32,
747        exptime: u32,
748    ) -> Result<(), Error> {
749        let key = key.as_ref();
750        let value = value.as_ref();
751        let encoded = encode_set(key, value, flags, exptime);
752
753        if !self.is_instrumented() {
754            let response = self.execute(&encoded).await?;
755            return match response {
756                McResponseBytes::Stored => Ok(()),
757                _ => Err(Error::UnexpectedResponse),
758            };
759        }
760
761        let send_ts = self.send_timestamp();
762        let start = Instant::now();
763        let response = self.execute(&encoded).await;
764        let latency_ns = self.finish_timing(send_ts, start);
765
766        let result = match response {
767            Ok(McResponseBytes::Stored) => Ok(()),
768            Ok(_) => Err(Error::UnexpectedResponse),
769            Err(e) => Err(e),
770        };
771        self.record(&CommandResult {
772            command: CommandType::Set,
773            latency_ns,
774            hit: None,
775            success: result.is_ok(),
776            ttfb_ns: None,
777        });
778        result
779    }
780
781    /// Store a key only if it does not already exist (ADD command).
782    /// Returns `true` if stored, `false` if the key already exists.
783    pub async fn add(
784        &mut self,
785        key: impl AsRef<[u8]>,
786        value: impl AsRef<[u8]>,
787    ) -> Result<bool, Error> {
788        let key = key.as_ref();
789        let value = value.as_ref();
790        let encoded = encode_add(key, value);
791        let response = self.execute(&encoded).await?;
792        match response {
793            McResponseBytes::Stored => Ok(true),
794            McResponseBytes::NotStored => Ok(false),
795            _ => Err(Error::UnexpectedResponse),
796        }
797    }
798
799    /// Store a key only if it already exists (REPLACE command).
800    /// Returns `true` if stored, `false` if the key does not exist.
801    pub async fn replace(
802        &mut self,
803        key: impl AsRef<[u8]>,
804        value: impl AsRef<[u8]>,
805    ) -> Result<bool, Error> {
806        let key = key.as_ref();
807        let value = value.as_ref();
808        let encoded = encode_request(&McRequest::Replace {
809            key,
810            value,
811            flags: 0,
812            exptime: 0,
813        });
814        let response = self.execute(&encoded).await?;
815        match response {
816            McResponseBytes::Stored => Ok(true),
817            McResponseBytes::NotStored => Ok(false),
818            _ => Err(Error::UnexpectedResponse),
819        }
820    }
821
822    /// Increment a numeric value by delta. Returns the new value after incrementing.
823    /// Returns `None` if the key does not exist.
824    pub async fn incr(&mut self, key: impl AsRef<[u8]>, delta: u64) -> Result<Option<u64>, Error> {
825        let key = key.as_ref();
826        let encoded = encode_request(&McRequest::incr(key, delta));
827        let response = self.execute(&encoded).await?;
828        match response {
829            McResponseBytes::Numeric(val) => Ok(Some(val)),
830            McResponseBytes::NotFound => Ok(None),
831            _ => Err(Error::UnexpectedResponse),
832        }
833    }
834
835    /// Decrement a numeric value by delta. Returns the new value after decrementing.
836    /// Returns `None` if the key does not exist.
837    pub async fn decr(&mut self, key: impl AsRef<[u8]>, delta: u64) -> Result<Option<u64>, Error> {
838        let key = key.as_ref();
839        let encoded = encode_request(&McRequest::decr(key, delta));
840        let response = self.execute(&encoded).await?;
841        match response {
842            McResponseBytes::Numeric(val) => Ok(Some(val)),
843            McResponseBytes::NotFound => Ok(None),
844            _ => Err(Error::UnexpectedResponse),
845        }
846    }
847
848    /// Append data to an existing item's value.
849    /// Returns `true` if stored, `false` if the key does not exist.
850    pub async fn append(
851        &mut self,
852        key: impl AsRef<[u8]>,
853        value: impl AsRef<[u8]>,
854    ) -> Result<bool, Error> {
855        let key = key.as_ref();
856        let value = value.as_ref();
857        let encoded = encode_request(&McRequest::append(key, value));
858        let response = self.execute(&encoded).await?;
859        match response {
860            McResponseBytes::Stored => Ok(true),
861            McResponseBytes::NotStored => Ok(false),
862            _ => Err(Error::UnexpectedResponse),
863        }
864    }
865
866    /// Prepend data to an existing item's value.
867    /// Returns `true` if stored, `false` if the key does not exist.
868    pub async fn prepend(
869        &mut self,
870        key: impl AsRef<[u8]>,
871        value: impl AsRef<[u8]>,
872    ) -> Result<bool, Error> {
873        let key = key.as_ref();
874        let value = value.as_ref();
875        let encoded = encode_request(&McRequest::prepend(key, value));
876        let response = self.execute(&encoded).await?;
877        match response {
878            McResponseBytes::Stored => Ok(true),
879            McResponseBytes::NotStored => Ok(false),
880            _ => Err(Error::UnexpectedResponse),
881        }
882    }
883
884    /// Compare-and-swap: store the value only if the CAS token matches.
885    /// Returns `Ok(true)` if stored, `Ok(false)` if the CAS token didn't match (EXISTS),
886    /// or `Err` if the key was not found or another error occurred.
887    pub async fn cas(
888        &mut self,
889        key: impl AsRef<[u8]>,
890        value: impl AsRef<[u8]>,
891        cas_unique: u64,
892    ) -> Result<bool, Error> {
893        let key = key.as_ref();
894        let value = value.as_ref();
895        let encoded = encode_request(&McRequest::cas(key, value, cas_unique));
896        let response = self.execute(&encoded).await?;
897        match response {
898            McResponseBytes::Stored => Ok(true),
899            McResponseBytes::Exists => Ok(false),
900            McResponseBytes::NotFound => Err(Error::Memcache("NOT_FOUND".into())),
901            _ => Err(Error::UnexpectedResponse),
902        }
903    }
904
905    /// Delete a key. Returns `true` if deleted, `false` if not found.
906    pub async fn delete(&mut self, key: impl AsRef<[u8]>) -> Result<bool, Error> {
907        let key = key.as_ref();
908        let encoded = encode_request(&McRequest::delete(key));
909
910        if !self.is_instrumented() {
911            let response = self.execute(&encoded).await?;
912            return match response {
913                McResponseBytes::Deleted => Ok(true),
914                McResponseBytes::NotFound => Ok(false),
915                _ => Err(Error::UnexpectedResponse),
916            };
917        }
918
919        let send_ts = self.send_timestamp();
920        let start = Instant::now();
921        let response = self.execute(&encoded).await;
922        let latency_ns = self.finish_timing(send_ts, start);
923
924        let result = match response {
925            Ok(McResponseBytes::Deleted) => Ok(true),
926            Ok(McResponseBytes::NotFound) => Ok(false),
927            Ok(_) => Err(Error::UnexpectedResponse),
928            Err(e) => Err(e),
929        };
930        self.record(&CommandResult {
931            command: CommandType::Delete,
932            latency_ns,
933            hit: None,
934            success: result.is_ok(),
935            ttfb_ns: None,
936        });
937        result
938    }
939
940    /// Flush all items from the cache.
941    pub async fn flush_all(&mut self) -> Result<(), Error> {
942        let encoded = encode_request(&McRequest::flush_all());
943        let response = self.execute(&encoded).await?;
944        match response {
945            McResponseBytes::Ok => Ok(()),
946            _ => Err(Error::UnexpectedResponse),
947        }
948    }
949
950    /// Get the server version string.
951    pub async fn version(&mut self) -> Result<String, Error> {
952        let encoded = encode_request(&McRequest::version());
953        let response = self.execute(&encoded).await?;
954        match response {
955            McResponseBytes::Version(v) => Ok(String::from_utf8_lossy(&v).into_owned()),
956            _ => Err(Error::UnexpectedResponse),
957        }
958    }
959
960    // -- Zero-copy SET -------------------------------------------------------
961
962    /// SET with zero-copy value via SendGuard. The guard pins value memory
963    /// until the kernel completes the send.
964    pub async fn set_with_guard<G: SendGuard>(
965        &mut self,
966        key: &[u8],
967        guard: G,
968        flags: u32,
969        exptime: u32,
970    ) -> Result<(), Error> {
971        if !self.is_instrumented() {
972            let (_, value_len) = guard.as_ptr_len();
973            let prefix = encode_set_guard_prefix(key, value_len as usize, flags, exptime);
974
975            self.conn.send_parts().build(move |b| {
976                b.copy(&prefix)
977                    .guard(GuardBox::new(guard))
978                    .copy(b"\r\n")
979                    .submit()
980            })?;
981
982            let response = self.read_response().await?;
983            check_error_bytes(&response)?;
984            return match response {
985                McResponseBytes::Stored => Ok(()),
986                _ => Err(Error::UnexpectedResponse),
987            };
988        }
989
990        let send_ts = self.send_timestamp();
991        let start = Instant::now();
992
993        let (_, value_len) = guard.as_ptr_len();
994        let prefix = encode_set_guard_prefix(key, value_len as usize, flags, exptime);
995
996        self.conn.send_parts().build(move |b| {
997            b.copy(&prefix)
998                .guard(GuardBox::new(guard))
999                .copy(b"\r\n")
1000                .submit()
1001        })?;
1002
1003        let response = self.read_response().await;
1004        let latency_ns = self.finish_timing(send_ts, start);
1005
1006        let result = match response {
1007            Ok(ref r) => {
1008                check_error_bytes(r)?;
1009                match r {
1010                    McResponseBytes::Stored => Ok(()),
1011                    _ => Err(Error::UnexpectedResponse),
1012                }
1013            }
1014            Err(e) => Err(e),
1015        };
1016        self.record(&CommandResult {
1017            command: CommandType::Set,
1018            latency_ns,
1019            hit: None,
1020            success: result.is_ok(),
1021            ttfb_ns: None,
1022        });
1023        result
1024    }
1025}
1026
1027// -- Zero-copy SET encoding helpers ------------------------------------------
1028
1029/// Encode memcache text SET prefix for guard-based sends.
1030///
1031/// Returns: `set {key} {flags} {exptime} {valuelen}\r\n`
1032/// The caller must append value bytes (via guard) + `\r\n` suffix.
1033fn encode_set_guard_prefix(key: &[u8], value_len: usize, flags: u32, exptime: u32) -> Vec<u8> {
1034    use std::io::Write;
1035    let mut buf = Vec::with_capacity(32 + key.len());
1036    buf.extend_from_slice(b"set ");
1037    buf.extend_from_slice(key);
1038    write!(buf, " {} {} {}\r\n", flags, exptime, value_len).unwrap();
1039    buf
1040}
1041
1042// -- Encoding helpers --------------------------------------------------------
1043
1044/// Encode a `McRequest` into a `Vec<u8>`.
1045pub(crate) fn encode_request(req: &McRequest<'_>) -> Vec<u8> {
1046    let size = match req {
1047        McRequest::Get { key } => 6 + key.len(),
1048        McRequest::Gets { keys } => 6 + keys.iter().map(|k| 1 + k.len()).sum::<usize>(),
1049        McRequest::Set { key, value, .. } | McRequest::Add { key, value, .. } => {
1050            41 + key.len() + value.len()
1051        }
1052        McRequest::Replace { key, value, .. } => 45 + key.len() + value.len(),
1053        McRequest::Incr { key, .. } | McRequest::Decr { key, .. } => 27 + key.len(),
1054        McRequest::Append { key, value } => 44 + key.len() + value.len(),
1055        McRequest::Prepend { key, value } => 45 + key.len() + value.len(),
1056        McRequest::Cas { key, value, .. } => 61 + key.len() + value.len(),
1057        McRequest::Delete { key } => 9 + key.len(),
1058        McRequest::FlushAll => 11,
1059        McRequest::Version => 9,
1060        McRequest::Quit => 6,
1061    };
1062    let mut buf = vec![0u8; size];
1063    let len = req.encode(&mut buf);
1064    buf.truncate(len);
1065    buf
1066}
1067
1068/// Encode a SET command into a `Vec<u8>`.
1069pub(crate) fn encode_set(key: &[u8], value: &[u8], flags: u32, exptime: u32) -> Vec<u8> {
1070    encode_request(&McRequest::Set {
1071        key,
1072        value,
1073        flags,
1074        exptime,
1075    })
1076}
1077
1078/// Encode an ADD command into a `Vec<u8>`.
1079pub(crate) fn encode_add(key: &[u8], value: &[u8]) -> Vec<u8> {
1080    encode_request(&McRequest::Add {
1081        key,
1082        value,
1083        flags: 0,
1084        exptime: 0,
1085    })
1086}
1087
1088/// Check a `ResponseBytes` for error variants and return an appropriate `Error`.
1089pub(crate) fn check_error_bytes(response: &McResponseBytes) -> Result<(), Error> {
1090    match response {
1091        McResponseBytes::Error => Err(Error::Memcache("ERROR".into())),
1092        McResponseBytes::ClientError(msg) => Err(Error::Memcache(format!(
1093            "CLIENT_ERROR {}",
1094            String::from_utf8_lossy(msg)
1095        ))),
1096        McResponseBytes::ServerError(msg) => Err(Error::Memcache(format!(
1097            "SERVER_ERROR {}",
1098            String::from_utf8_lossy(msg)
1099        ))),
1100        _ => Ok(()),
1101    }
1102}
1103
1104// ── Kernel timestamp helper ─────────────────────────────────────────────
1105
1106#[cfg(feature = "timestamps")]
1107fn now_realtime_ns() -> u64 {
1108    let mut ts = libc::timespec {
1109        tv_sec: 0,
1110        tv_nsec: 0,
1111    };
1112    unsafe {
1113        libc::clock_gettime(libc::CLOCK_REALTIME, &mut ts);
1114    }
1115    ts.tv_sec as u64 * 1_000_000_000 + ts.tv_nsec as u64
1116}