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