pub mod pool;
pub mod sharded;
pub use pool::{Pool, PoolConfig};
pub use sharded::{ShardedClient, ShardedConfig};
use std::cell::Cell;
use std::collections::VecDeque;
use std::io;
use std::time::Instant;
use bytes::Bytes;
use memcache_proto::{Request as McRequest, ResponseBytes as McResponseBytes};
use ringline::{ConnCtx, GuardBox, ParseResult, SendGuard};
type ResultCallback = Box<dyn Fn(&CommandResult)>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("connection closed")]
ConnectionClosed,
#[error("memcache error: {0}")]
Memcache(String),
#[error("unexpected response")]
UnexpectedResponse,
#[error("protocol error: {0}")]
Protocol(#[from] memcache_proto::ParseError),
#[error("io error: {0}")]
Io(#[from] io::Error),
#[error("all connections failed")]
AllConnectionsFailed,
#[error("no pending operations")]
NoPending,
}
#[derive(Debug, Clone)]
pub struct Value {
pub data: Bytes,
pub flags: u32,
}
#[derive(Debug, Clone)]
pub struct GetValue {
pub key: Bytes,
pub data: Bytes,
pub flags: u32,
pub cas: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CommandType {
Get,
Set,
Delete,
Other,
}
#[derive(Debug, Clone)]
pub struct CommandResult {
pub command: CommandType,
pub latency_ns: u64,
pub hit: Option<bool>,
pub success: bool,
pub ttfb_ns: Option<u64>,
pub tx_bytes: u32,
pub rx_bytes: u32,
}
#[cfg(feature = "metrics")]
pub struct ClientMetrics {
pub latency: histogram::Histogram,
pub get_latency: histogram::Histogram,
pub set_latency: histogram::Histogram,
pub del_latency: histogram::Histogram,
pub requests: u64,
pub errors: u64,
pub hits: u64,
pub misses: u64,
}
#[cfg(feature = "metrics")]
impl ClientMetrics {
fn new() -> Self {
Self {
latency: histogram::Histogram::new(7, 64).unwrap(),
get_latency: histogram::Histogram::new(7, 64).unwrap(),
set_latency: histogram::Histogram::new(7, 64).unwrap(),
del_latency: histogram::Histogram::new(7, 64).unwrap(),
requests: 0,
errors: 0,
hits: 0,
misses: 0,
}
}
fn record(&mut self, result: &CommandResult) {
self.requests += 1;
let _ = self.latency.increment(result.latency_ns);
if !result.success {
self.errors += 1;
}
match result.command {
CommandType::Get => {
let _ = self.get_latency.increment(result.latency_ns);
match result.hit {
Some(true) => self.hits += 1,
Some(false) => self.misses += 1,
None => {}
}
}
CommandType::Set => {
let _ = self.set_latency.increment(result.latency_ns);
}
CommandType::Delete => {
let _ = self.del_latency.increment(result.latency_ns);
}
_ => {}
}
}
}
enum PendingOpKind {
Get,
Set,
Delete,
}
struct PendingOp {
kind: PendingOpKind,
send_ts: u64,
start: Option<Instant>,
user_data: u64,
tx_bytes: u32,
}
pub enum CompletedOp {
Get {
result: Result<Option<Value>, Error>,
user_data: u64,
latency_ns: u64,
},
Set {
result: Result<(), Error>,
user_data: u64,
latency_ns: u64,
},
Delete {
result: Result<bool, Error>,
user_data: u64,
latency_ns: u64,
},
}
pub struct ClientBuilder {
conn: ConnCtx,
on_result: Option<ResultCallback>,
#[cfg(feature = "timestamps")]
use_kernel_ts: bool,
#[cfg(feature = "metrics")]
with_metrics: bool,
}
impl ClientBuilder {
pub(crate) fn new(conn: ConnCtx) -> Self {
Self {
conn,
on_result: None,
#[cfg(feature = "timestamps")]
use_kernel_ts: false,
#[cfg(feature = "metrics")]
with_metrics: false,
}
}
pub fn on_result<F: Fn(&CommandResult) + 'static>(mut self, f: F) -> Self {
self.on_result = Some(Box::new(f));
self
}
#[cfg(feature = "timestamps")]
pub fn kernel_timestamps(mut self, enabled: bool) -> Self {
self.use_kernel_ts = enabled;
self
}
#[cfg(feature = "metrics")]
pub fn with_metrics(mut self) -> Self {
self.with_metrics = true;
self
}
pub fn build(self) -> Client {
Client {
conn: self.conn,
on_result: self.on_result,
pending: VecDeque::new(),
last_rx_bytes: Cell::new(0),
#[cfg(feature = "timestamps")]
use_kernel_ts: self.use_kernel_ts,
#[cfg(feature = "metrics")]
metrics: if self.with_metrics {
Some(ClientMetrics::new())
} else {
None
},
}
}
}
pub struct Client {
conn: ConnCtx,
on_result: Option<ResultCallback>,
pending: VecDeque<PendingOp>,
last_rx_bytes: Cell<u32>,
#[cfg(feature = "timestamps")]
use_kernel_ts: bool,
#[cfg(feature = "metrics")]
metrics: Option<ClientMetrics>,
}
impl Client {
pub fn new(conn: ConnCtx) -> Self {
Self {
conn,
on_result: None,
pending: VecDeque::new(),
last_rx_bytes: Cell::new(0),
#[cfg(feature = "timestamps")]
use_kernel_ts: false,
#[cfg(feature = "metrics")]
metrics: None,
}
}
pub fn builder(conn: ConnCtx) -> ClientBuilder {
ClientBuilder::new(conn)
}
pub fn conn(&self) -> ConnCtx {
self.conn
}
#[cfg(feature = "metrics")]
pub fn metrics(&self) -> Option<&ClientMetrics> {
self.metrics.as_ref()
}
#[cfg(feature = "metrics")]
pub fn metrics_mut(&mut self) -> Option<&mut ClientMetrics> {
self.metrics.as_mut()
}
#[inline]
fn is_instrumented(&self) -> bool {
if self.on_result.is_some() {
return true;
}
#[cfg(feature = "metrics")]
if self.metrics.is_some() {
return true;
}
false
}
#[cfg(feature = "timestamps")]
#[inline]
fn send_timestamp(&self) -> u64 {
if self.use_kernel_ts {
now_realtime_ns()
} else {
0
}
}
#[cfg(not(feature = "timestamps"))]
#[inline]
fn send_timestamp(&self) -> u64 {
0
}
#[cfg(feature = "timestamps")]
#[inline]
fn finish_timing(&self, send_ts: u64, start: Instant) -> u64 {
if self.use_kernel_ts {
let recv_ts = self.conn.recv_timestamp();
if recv_ts > 0 && recv_ts > send_ts {
return recv_ts - send_ts;
}
}
start.elapsed().as_nanos() as u64
}
#[cfg(not(feature = "timestamps"))]
#[inline]
fn finish_timing(&self, _send_ts: u64, start: Instant) -> u64 {
start.elapsed().as_nanos() as u64
}
fn record(&mut self, result: &CommandResult) {
if let Some(ref cb) = self.on_result {
cb(result);
}
#[cfg(feature = "metrics")]
if let Some(ref mut m) = self.metrics {
m.record(result);
}
}
#[inline]
fn timing_start(&self) -> (u64, Option<Instant>) {
if self.is_instrumented() {
(self.send_timestamp(), Some(Instant::now()))
} else {
(0, None)
}
}
#[cfg(feature = "timestamps")]
#[inline]
fn compute_ttfb(&self, send_ts: u64) -> Option<u64> {
if self.use_kernel_ts {
let recv_ts = self.conn.recv_timestamp();
if recv_ts > 0 && recv_ts > send_ts {
return Some(recv_ts - send_ts);
}
}
None
}
#[cfg(not(feature = "timestamps"))]
#[inline]
fn compute_ttfb(&self, _send_ts: u64) -> Option<u64> {
None
}
pub fn pending_count(&self) -> usize {
self.pending.len()
}
pub fn fire_get(&mut self, key: &[u8], user_data: u64) -> Result<(), Error> {
let encoded = encode_request(&McRequest::get(key));
let tx_bytes = encoded.len() as u32;
self.conn.send_nowait(&encoded)?;
let (send_ts, start) = self.timing_start();
self.pending.push_back(PendingOp {
kind: PendingOpKind::Get,
send_ts,
start,
user_data,
tx_bytes,
});
Ok(())
}
pub fn fire_set(
&mut self,
key: &[u8],
value: &[u8],
flags: u32,
exptime: u32,
user_data: u64,
) -> Result<(), Error> {
let encoded = encode_set(key, value, flags, exptime);
let tx_bytes = encoded.len() as u32;
self.conn.send_nowait(&encoded)?;
let (send_ts, start) = self.timing_start();
self.pending.push_back(PendingOp {
kind: PendingOpKind::Set,
send_ts,
start,
user_data,
tx_bytes,
});
Ok(())
}
pub fn fire_set_with_guard<G: SendGuard>(
&mut self,
key: &[u8],
guard: G,
flags: u32,
exptime: u32,
user_data: u64,
) -> Result<(), Error> {
let (_, value_len) = guard.as_ptr_len();
let prefix = encode_set_guard_prefix(key, value_len as usize, flags, exptime);
let tx_bytes = (prefix.len() + value_len as usize + 2) as u32;
self.conn.send_parts().build(move |b| {
b.copy(&prefix)
.guard(GuardBox::new(guard))
.copy(b"\r\n")
.submit()
})?;
let (send_ts, start) = self.timing_start();
self.pending.push_back(PendingOp {
kind: PendingOpKind::Set,
send_ts,
start,
user_data,
tx_bytes,
});
Ok(())
}
pub fn fire_delete(&mut self, key: &[u8], user_data: u64) -> Result<(), Error> {
let encoded = encode_request(&McRequest::delete(key));
let tx_bytes = encoded.len() as u32;
self.conn.send_nowait(&encoded)?;
let (send_ts, start) = self.timing_start();
self.pending.push_back(PendingOp {
kind: PendingOpKind::Delete,
send_ts,
start,
user_data,
tx_bytes,
});
Ok(())
}
pub async fn recv(&mut self) -> Result<CompletedOp, Error> {
let pending = self.pending.pop_front().ok_or(Error::NoPending)?;
let ttfb_ns = self.compute_ttfb(pending.send_ts);
let response = match self.read_response().await {
Ok(v) => v,
Err(e) => {
self.pending.clear();
return Err(e);
}
};
let latency_ns = match pending.start {
Some(start) => self.finish_timing(pending.send_ts, start),
None => 0,
};
let rx_bytes = self.last_rx_bytes.get();
let tx_bytes = pending.tx_bytes;
let op = match pending.kind {
PendingOpKind::Get => {
let result = match check_error_bytes(&response) {
Err(e) => Err(e),
Ok(()) => match response {
McResponseBytes::Values(mut values) => {
if values.is_empty() {
Ok(None)
} else {
let v = values.swap_remove(0);
Ok(Some(Value {
data: v.data,
flags: v.flags,
}))
}
}
_ => Err(Error::UnexpectedResponse),
},
};
let (success, hit) = match &result {
Ok(Some(_)) => (true, Some(true)),
Ok(None) => (true, Some(false)),
Err(_) => (false, None),
};
self.record(&CommandResult {
command: CommandType::Get,
latency_ns,
hit,
success,
ttfb_ns,
tx_bytes,
rx_bytes,
});
CompletedOp::Get {
result,
user_data: pending.user_data,
latency_ns,
}
}
PendingOpKind::Set => {
let result = match check_error_bytes(&response) {
Err(e) => Err(e),
Ok(()) => match response {
McResponseBytes::Stored => Ok(()),
_ => Err(Error::UnexpectedResponse),
},
};
self.record(&CommandResult {
command: CommandType::Set,
latency_ns,
hit: None,
success: result.is_ok(),
ttfb_ns,
tx_bytes,
rx_bytes,
});
CompletedOp::Set {
result,
user_data: pending.user_data,
latency_ns,
}
}
PendingOpKind::Delete => {
let result = match check_error_bytes(&response) {
Err(e) => Err(e),
Ok(()) => match response {
McResponseBytes::Deleted => Ok(true),
McResponseBytes::NotFound => Ok(false),
_ => Err(Error::UnexpectedResponse),
},
};
self.record(&CommandResult {
command: CommandType::Delete,
latency_ns,
hit: None,
success: result.is_ok(),
ttfb_ns,
tx_bytes,
rx_bytes,
});
CompletedOp::Delete {
result,
user_data: pending.user_data,
latency_ns,
}
}
};
Ok(op)
}
pub(crate) async fn read_response(&self) -> Result<McResponseBytes, Error> {
let mut result: Option<Result<McResponseBytes, Error>> = None;
let n = self
.conn
.with_bytes(|bytes| {
let len = bytes.len();
match McResponseBytes::parse(bytes) {
Ok((response, consumed)) => {
result = Some(Ok(response));
ParseResult::Consumed(consumed)
}
Err(e) if e.is_incomplete() => ParseResult::Consumed(0),
Err(e) => {
result = Some(Err(Error::Protocol(e)));
ParseResult::Consumed(len)
}
}
})
.await;
self.last_rx_bytes.set(n as u32);
if n == 0 {
return result.unwrap_or(Err(Error::ConnectionClosed));
}
result.unwrap()
}
async fn execute(&self, encoded: &[u8]) -> Result<McResponseBytes, Error> {
self.conn.send(encoded)?;
let response = self.read_response().await?;
check_error_bytes(&response)?;
Ok(response)
}
pub async fn get(&mut self, key: impl AsRef<[u8]>) -> Result<Option<Value>, Error> {
let key = key.as_ref();
let encoded = encode_request(&McRequest::get(key));
if !self.is_instrumented() {
let response = self.execute(&encoded).await?;
return match response {
McResponseBytes::Values(mut values) => {
if values.is_empty() {
Ok(None)
} else {
let v = values.swap_remove(0);
Ok(Some(Value {
data: v.data,
flags: v.flags,
}))
}
}
_ => Err(Error::UnexpectedResponse),
};
}
let tx_bytes = encoded.len() as u32;
let send_ts = self.send_timestamp();
let start = Instant::now();
let response = self.execute(&encoded).await;
let latency_ns = self.finish_timing(send_ts, start);
let rx_bytes = self.last_rx_bytes.get();
let result = match response {
Ok(McResponseBytes::Values(mut values)) => {
if values.is_empty() {
Ok(None)
} else {
let v = values.swap_remove(0);
Ok(Some(Value {
data: v.data,
flags: v.flags,
}))
}
}
Ok(_) => Err(Error::UnexpectedResponse),
Err(e) => Err(e),
};
let (success, hit) = match &result {
Ok(Some(_)) => (true, Some(true)),
Ok(None) => (true, Some(false)),
Err(_) => (false, None),
};
self.record(&CommandResult {
command: CommandType::Get,
latency_ns,
hit,
success,
ttfb_ns: None,
tx_bytes,
rx_bytes,
});
result
}
pub async fn gets(&mut self, keys: &[&[u8]]) -> Result<Vec<GetValue>, Error> {
if keys.is_empty() {
return Ok(Vec::new());
}
let encoded = encode_request(&McRequest::gets(keys));
let response = self.execute(&encoded).await?;
match response {
McResponseBytes::Values(values) => Ok(values
.into_iter()
.map(|v| GetValue {
key: v.key,
data: v.data,
flags: v.flags,
cas: v.cas,
})
.collect()),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn set(
&mut self,
key: impl AsRef<[u8]>,
value: impl AsRef<[u8]>,
) -> Result<(), Error> {
self.set_with_options(key, value, 0, 0).await
}
pub async fn set_with_options(
&mut self,
key: impl AsRef<[u8]>,
value: impl AsRef<[u8]>,
flags: u32,
exptime: u32,
) -> Result<(), Error> {
let key = key.as_ref();
let value = value.as_ref();
let encoded = encode_set(key, value, flags, exptime);
if !self.is_instrumented() {
let response = self.execute(&encoded).await?;
return match response {
McResponseBytes::Stored => Ok(()),
_ => Err(Error::UnexpectedResponse),
};
}
let tx_bytes = encoded.len() as u32;
let send_ts = self.send_timestamp();
let start = Instant::now();
let response = self.execute(&encoded).await;
let latency_ns = self.finish_timing(send_ts, start);
let rx_bytes = self.last_rx_bytes.get();
let result = match response {
Ok(McResponseBytes::Stored) => Ok(()),
Ok(_) => Err(Error::UnexpectedResponse),
Err(e) => Err(e),
};
self.record(&CommandResult {
command: CommandType::Set,
latency_ns,
hit: None,
success: result.is_ok(),
ttfb_ns: None,
tx_bytes,
rx_bytes,
});
result
}
pub async fn add(
&mut self,
key: impl AsRef<[u8]>,
value: impl AsRef<[u8]>,
) -> Result<bool, Error> {
let key = key.as_ref();
let value = value.as_ref();
let encoded = encode_add(key, value);
let response = self.execute(&encoded).await?;
match response {
McResponseBytes::Stored => Ok(true),
McResponseBytes::NotStored => Ok(false),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn replace(
&mut self,
key: impl AsRef<[u8]>,
value: impl AsRef<[u8]>,
) -> Result<bool, Error> {
let key = key.as_ref();
let value = value.as_ref();
let encoded = encode_request(&McRequest::Replace {
key,
value,
flags: 0,
exptime: 0,
});
let response = self.execute(&encoded).await?;
match response {
McResponseBytes::Stored => Ok(true),
McResponseBytes::NotStored => Ok(false),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn incr(&mut self, key: impl AsRef<[u8]>, delta: u64) -> Result<Option<u64>, Error> {
let key = key.as_ref();
let encoded = encode_request(&McRequest::incr(key, delta));
let response = self.execute(&encoded).await?;
match response {
McResponseBytes::Numeric(val) => Ok(Some(val)),
McResponseBytes::NotFound => Ok(None),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn decr(&mut self, key: impl AsRef<[u8]>, delta: u64) -> Result<Option<u64>, Error> {
let key = key.as_ref();
let encoded = encode_request(&McRequest::decr(key, delta));
let response = self.execute(&encoded).await?;
match response {
McResponseBytes::Numeric(val) => Ok(Some(val)),
McResponseBytes::NotFound => Ok(None),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn append(
&mut self,
key: impl AsRef<[u8]>,
value: impl AsRef<[u8]>,
) -> Result<bool, Error> {
let key = key.as_ref();
let value = value.as_ref();
let encoded = encode_request(&McRequest::append(key, value));
let response = self.execute(&encoded).await?;
match response {
McResponseBytes::Stored => Ok(true),
McResponseBytes::NotStored => Ok(false),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn prepend(
&mut self,
key: impl AsRef<[u8]>,
value: impl AsRef<[u8]>,
) -> Result<bool, Error> {
let key = key.as_ref();
let value = value.as_ref();
let encoded = encode_request(&McRequest::prepend(key, value));
let response = self.execute(&encoded).await?;
match response {
McResponseBytes::Stored => Ok(true),
McResponseBytes::NotStored => Ok(false),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn cas(
&mut self,
key: impl AsRef<[u8]>,
value: impl AsRef<[u8]>,
cas_unique: u64,
) -> Result<bool, Error> {
let key = key.as_ref();
let value = value.as_ref();
let encoded = encode_request(&McRequest::cas(key, value, cas_unique));
let response = self.execute(&encoded).await?;
match response {
McResponseBytes::Stored => Ok(true),
McResponseBytes::Exists => Ok(false),
McResponseBytes::NotFound => Err(Error::Memcache("NOT_FOUND".into())),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn delete(&mut self, key: impl AsRef<[u8]>) -> Result<bool, Error> {
let key = key.as_ref();
let encoded = encode_request(&McRequest::delete(key));
if !self.is_instrumented() {
let response = self.execute(&encoded).await?;
return match response {
McResponseBytes::Deleted => Ok(true),
McResponseBytes::NotFound => Ok(false),
_ => Err(Error::UnexpectedResponse),
};
}
let tx_bytes = encoded.len() as u32;
let send_ts = self.send_timestamp();
let start = Instant::now();
let response = self.execute(&encoded).await;
let latency_ns = self.finish_timing(send_ts, start);
let rx_bytes = self.last_rx_bytes.get();
let result = match response {
Ok(McResponseBytes::Deleted) => Ok(true),
Ok(McResponseBytes::NotFound) => Ok(false),
Ok(_) => Err(Error::UnexpectedResponse),
Err(e) => Err(e),
};
self.record(&CommandResult {
command: CommandType::Delete,
latency_ns,
hit: None,
success: result.is_ok(),
ttfb_ns: None,
tx_bytes,
rx_bytes,
});
result
}
pub async fn flush_all(&mut self) -> Result<(), Error> {
let encoded = encode_request(&McRequest::flush_all());
let response = self.execute(&encoded).await?;
match response {
McResponseBytes::Ok => Ok(()),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn version(&mut self) -> Result<String, Error> {
let encoded = encode_request(&McRequest::version());
let response = self.execute(&encoded).await?;
match response {
McResponseBytes::Version(v) => Ok(String::from_utf8_lossy(&v).into_owned()),
_ => Err(Error::UnexpectedResponse),
}
}
pub async fn set_with_guard<G: SendGuard>(
&mut self,
key: &[u8],
guard: G,
flags: u32,
exptime: u32,
) -> Result<(), Error> {
if !self.is_instrumented() {
let (_, value_len) = guard.as_ptr_len();
let prefix = encode_set_guard_prefix(key, value_len as usize, flags, exptime);
self.conn.send_parts().build(move |b| {
b.copy(&prefix)
.guard(GuardBox::new(guard))
.copy(b"\r\n")
.submit()
})?;
let response = self.read_response().await?;
check_error_bytes(&response)?;
return match response {
McResponseBytes::Stored => Ok(()),
_ => Err(Error::UnexpectedResponse),
};
}
let (_, value_len) = guard.as_ptr_len();
let prefix = encode_set_guard_prefix(key, value_len as usize, flags, exptime);
let tx_bytes = (prefix.len() + value_len as usize + 2) as u32;
let send_ts = self.send_timestamp();
let start = Instant::now();
self.conn.send_parts().build(move |b| {
b.copy(&prefix)
.guard(GuardBox::new(guard))
.copy(b"\r\n")
.submit()
})?;
let response = self.read_response().await;
let latency_ns = self.finish_timing(send_ts, start);
let rx_bytes = self.last_rx_bytes.get();
let result = match response {
Ok(ref r) => {
check_error_bytes(r)?;
match r {
McResponseBytes::Stored => Ok(()),
_ => Err(Error::UnexpectedResponse),
}
}
Err(e) => Err(e),
};
self.record(&CommandResult {
command: CommandType::Set,
latency_ns,
hit: None,
success: result.is_ok(),
ttfb_ns: None,
tx_bytes,
rx_bytes,
});
result
}
}
fn encode_set_guard_prefix(key: &[u8], value_len: usize, flags: u32, exptime: u32) -> Vec<u8> {
use std::io::Write;
let mut buf = Vec::with_capacity(32 + key.len());
buf.extend_from_slice(b"set ");
buf.extend_from_slice(key);
write!(buf, " {} {} {}\r\n", flags, exptime, value_len).unwrap();
buf
}
pub(crate) fn encode_request(req: &McRequest<'_>) -> Vec<u8> {
let size = match req {
McRequest::Get { key } => 6 + key.len(),
McRequest::Gets { keys } => 6 + keys.iter().map(|k| 1 + k.len()).sum::<usize>(),
McRequest::Set { key, value, .. } | McRequest::Add { key, value, .. } => {
41 + key.len() + value.len()
}
McRequest::Replace { key, value, .. } => 45 + key.len() + value.len(),
McRequest::Incr { key, .. } | McRequest::Decr { key, .. } => 27 + key.len(),
McRequest::Append { key, value } => 44 + key.len() + value.len(),
McRequest::Prepend { key, value } => 45 + key.len() + value.len(),
McRequest::Cas { key, value, .. } => 61 + key.len() + value.len(),
McRequest::Delete { key } => 9 + key.len(),
McRequest::FlushAll => 11,
McRequest::Version => 9,
McRequest::Quit => 6,
};
let mut buf = vec![0u8; size];
let len = req.encode(&mut buf);
buf.truncate(len);
buf
}
pub(crate) fn encode_set(key: &[u8], value: &[u8], flags: u32, exptime: u32) -> Vec<u8> {
encode_request(&McRequest::Set {
key,
value,
flags,
exptime,
})
}
pub(crate) fn encode_add(key: &[u8], value: &[u8]) -> Vec<u8> {
encode_request(&McRequest::Add {
key,
value,
flags: 0,
exptime: 0,
})
}
pub(crate) fn check_error_bytes(response: &McResponseBytes) -> Result<(), Error> {
match response {
McResponseBytes::Error => Err(Error::Memcache("ERROR".into())),
McResponseBytes::ClientError(msg) => Err(Error::Memcache(format!(
"CLIENT_ERROR {}",
String::from_utf8_lossy(msg)
))),
McResponseBytes::ServerError(msg) => Err(Error::Memcache(format!(
"SERVER_ERROR {}",
String::from_utf8_lossy(msg)
))),
_ => Ok(()),
}
}
#[cfg(feature = "timestamps")]
fn now_realtime_ns() -> u64 {
let mut ts = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
unsafe {
libc::clock_gettime(libc::CLOCK_REALTIME, &mut ts);
}
ts.tv_sec as u64 * 1_000_000_000 + ts.tv_nsec as u64
}