Skip to main content

pb_jelly/
buffer.rs

1//! This module exposes two fundamental concepts:
2//!
3//! [PbBufferReader]
4//! A [PbBufferReader] is something which a [Message] can be deserialized from. In the common case,
5//! this means that the relevant bytes are copied out of the underlying store and copied into an
6//! appropriate struct which implements [Message].
7//!
8//! [PbBufferWriter]
9//! A [PbBufferWriter] is something which a [Message] can be serialized to. In the common case, this
10//! means that the relevant bytes are copied out of the concrete struct and into the underlying
11//! data store.
12//!
13//! # Zerocopy serialization and deserialization and `Lazy`
14//!
15//! There are cases where we want to minimize the number of times we copy the data contained within
16//! a message. Especially on resource-constrained hardware (mostly MP OSDs), we want to avoid the cost
17//! of copying large buffers during serialization and deserialization.
18//!
19//! To support this, messages may contain zerocopy fields using the [`Lazy`] type.
20//! `pb-jelly-gen` may generate these using the `blob`, `grpc_slices`, or `zero_copy` options; they
21//! use different underlying types, which must implement [PbBuffer], but they all behave similarly.
22//!
23//! [PbBufferReader] and a [PbBufferWriter] have the opportunity to recognize [Lazy] fields.
24//! At deserialization time, if [PbBufferReader] is used with a compatible [Lazy] field, instead of
25//! allocating, it may simply store a reference to its underlying input buffer in the [Lazy].
26//! Similarly, at serialization time, a [PbBufferWriter] used with a compatible [Lazy] may copy a
27//! reference to the [Lazy] field into its output buffer, rather than copying its content.
28//!
29//! Request (bytes on the wire)
30//!     |
31//!     v
32//! RPC Framework (with an underlying allocator, e.g. blob::Blob or grpc::Slice)
33//!     |
34//!     v
35//! BR: [PbBufferReader] deserializes the struct using RPC framework's allocator.
36//!     |
37//!     v
38//! Request (concrete struct containing a [Lazy] field)
39//!     |
40//!     v
41//! RPC handler (doesn't modify the [Lazy] field)
42//!     |
43//!     v
44//! Response (concrete struct containing a [Lazy] field)
45//!     |
46//!     v
47//! BW: [PbBufferWriter] serializes the struct using RPC framework's allocator.
48//!     |
49//!     v
50//! RPC Framework (with an underlying allocator, e.g. blob::Blob or grpc::Slice)
51//!     |
52//!     v
53//! Response (bytes on the wire).
54//!
55//!
56//! In the status quo, the behavior is as follows:
57//!
58//! `blob_pb::WrappedBlob` and `blob_pb::VecSlice` allow zero-copy deserialization -> serialization,
59//! provided that their respective [PbBufferWriter]s are used.
60//! Converting from `blob_pb::WrappedBlob` to a `blob_pb::VecSlice` is zero-copy.
61//! Converting from a `blob_pb::VecSlice` to a `blob_pb::Blob` requires a single copy.
62
63use std::any::Any;
64use std::fmt::{
65    self,
66    Debug,
67};
68use std::io::{
69    Cursor,
70    Result,
71    Write,
72};
73
74use bytes::{
75    Buf,
76    Bytes,
77};
78
79use super::{
80    Message,
81    Reflection,
82};
83
84/// A stand-in trait for any backing buffer store.
85/// `PbBuffer`s are expected to own references to the data they reference, and should be cheap
86/// (constant-time) to clone.
87#[allow(clippy::len_without_is_empty)]
88pub trait PbBuffer: Any + Sized {
89    /// Returns the length of the data contained in this buffer.
90    fn len(&self) -> usize;
91    /// Fallback method to read the contents of `self`. This method is expected to write exactly
92    /// `self.len()` bytes into `writer`, or fail with an error.
93    ///
94    /// This method is used to write `Lazy` fields to incompatible [`PbBufferWriter`]s.
95    fn copy_to_writer<W: Write + ?Sized>(&self, writer: &mut W) -> Result<()>;
96    /// Fallback method to create an instance of this `PbBuffer`.
97    ///
98    /// This method is used to read `Lazy` fields from incompatible [`PbBufferReader`]s.
99    fn copy_from_reader<B: Buf + ?Sized>(reader: &mut B) -> Result<Self>;
100}
101
102/// If `B1` and `B2` are the same type, returns a function to cast `B1 -> B2`; otherwise None.
103/// Used to implement [PbBuffer] casting.
104pub fn type_is<B1: 'static, B2: 'static>() -> Option<fn(B1) -> B2> {
105    let f: fn(B1) -> B1 = |x| x;
106    // If B1 = B2, then this cast should succeed!
107    (&f as &dyn Any).downcast_ref::<fn(B1) -> B2>().copied()
108}
109
110/// All concrete types which are used for deserialization should implement
111/// [PbBufferReader], which includes functions to convert to and from [PbBuffer].
112pub trait PbBufferReader: Buf {
113    /// Attempt to read into a compatible [PbBuffer], avoiding a copy if possible.
114    /// The implementation should dispatch on the type `B`. If unsupported,
115    /// the reader may fall back to [PbBuffer::copy_from_reader].
116    fn read_buffer<B: PbBuffer>(&mut self) -> Result<B> {
117        B::copy_from_reader(self)
118    }
119
120    /// Advance the interal cursor by `at`, and return a [PbBufferReader] corresponding to the
121    /// traversed indices (i.e. self.position..self.position + at).
122    fn split(&mut self, at: usize) -> Self;
123}
124
125/// All concrete types used for serialization should implement [PbBufferWriter] in order to support
126/// serializing [Lazy] fields without copies.
127pub trait PbBufferWriter: Write {
128    /// Attempt to write a zerocopy buffer into `self`. If `B` is not zero-copy-supported
129    /// by the [PbBufferWriter], this may read/copy the bytes out from `buf`.
130    fn write_buffer<B: PbBuffer>(&mut self, buf: &B) -> Result<()>;
131}
132
133/// A wrapper around a [PbBuffer], which implements [Message].
134#[derive(Clone, PartialEq)]
135pub struct Lazy<B> {
136    // TODO: Make this not an `Option` by giving `VecSlice` a cheap `Default` impl
137    contents: Option<B>,
138}
139
140impl<B> Default for Lazy<B> {
141    fn default() -> Self {
142        Self { contents: None }
143    }
144}
145
146impl<B> Lazy<B> {
147    pub fn new(r: B) -> Self {
148        Self { contents: Some(r) }
149    }
150
151    pub fn into_buffer(self) -> B
152    where
153        B: Default,
154    {
155        self.contents.unwrap_or_default()
156    }
157}
158
159impl<B> Debug for Lazy<B> {
160    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161        f.debug_struct("Lazy")
162            .field("contents", &self.contents.as_ref().map(|_| "_"))
163            .finish()
164    }
165}
166
167impl<B: PbBuffer + PartialEq> Message for Lazy<B> {
168    fn compute_size(&self) -> usize {
169        self.contents.as_ref().map(PbBuffer::len).unwrap_or(0)
170    }
171
172    fn compute_grpc_slices_size(&self) -> usize {
173        self.contents.as_ref().map(PbBuffer::len).unwrap_or(0)
174    }
175
176    fn serialize<W: PbBufferWriter>(&self, w: &mut W) -> Result<()> {
177        if let Some(ref contents) = self.contents {
178            w.write_buffer(contents)?;
179        }
180        Ok(())
181    }
182
183    fn deserialize<R: PbBufferReader>(&mut self, r: &mut R) -> Result<()> {
184        self.contents = Some(r.read_buffer()?);
185        Ok(())
186    }
187}
188
189impl<B: PbBuffer + PartialEq> Reflection for Lazy<B> {}
190
191impl<'a> PbBufferReader for Cursor<&'a [u8]> {
192    fn split(&mut self, at: usize) -> Self {
193        let pos = self.position() as usize;
194        self.advance(at);
195        let new_slice = &self.get_ref()[pos..pos + at];
196        Self::new(new_slice)
197    }
198}
199
200impl PbBuffer for Bytes {
201    #[inline]
202    fn len(&self) -> usize {
203        self.len()
204    }
205
206    fn copy_to_writer<W: Write + ?Sized>(&self, writer: &mut W) -> Result<()> {
207        writer.write_all(&self)
208    }
209
210    fn copy_from_reader<B: Buf + ?Sized>(reader: &mut B) -> Result<Self> {
211        let len = reader.remaining();
212        Ok(reader.copy_to_bytes(len))
213    }
214}
215
216impl PbBufferReader for Cursor<Bytes> {
217    fn read_buffer<B: PbBuffer>(&mut self) -> Result<B> {
218        if let Some(cast) = type_is::<Bytes, B>() {
219            let bytes = self.get_ref().slice((self.position() as usize)..);
220            Ok(cast(bytes))
221        } else {
222            B::copy_from_reader(self)
223        }
224    }
225
226    #[inline]
227    fn split(&mut self, at: usize) -> Self {
228        let pos = self.position() as usize;
229        self.advance(at);
230        let new_slice = self.get_ref().slice(pos..(pos + at));
231        Self::new(new_slice)
232    }
233}
234
235impl<'a> PbBufferWriter for Cursor<&'a mut Vec<u8>> {
236    /// Note: this implementation freely copies the data out of `buf`.
237    #[inline]
238    fn write_buffer<B: PbBuffer>(&mut self, buf: &B) -> Result<()> {
239        buf.copy_to_writer(self)
240    }
241}
242
243impl<'a> PbBufferWriter for Cursor<&'a mut [u8]> {
244    /// Note: this implementation freely copies the data out of `buf`.
245    #[inline]
246    fn write_buffer<B: PbBuffer>(&mut self, buf: &B) -> Result<()> {
247        buf.copy_to_writer(self)
248    }
249}
250
251/// A wrapper around a [Write] which copies all [Lazy] data into the underlying [Write]r.
252pub struct CopyWriter<'a, W: Write> {
253    pub writer: &'a mut W,
254}
255
256impl<'a, W: Write + 'a> Write for CopyWriter<'a, W> {
257    #[inline]
258    fn write(&mut self, buf: &[u8]) -> Result<usize> {
259        self.writer.write(buf)
260    }
261
262    #[inline]
263    fn flush(&mut self) -> Result<()> {
264        self.writer.flush()
265    }
266}
267
268impl<'a, W: Write + 'a> PbBufferWriter for CopyWriter<'a, W> {
269    /// Note: this implementation freely copies the data out of `buf`.
270    #[inline]
271    fn write_buffer<B: PbBuffer>(&mut self, buf: &B) -> Result<()> {
272        buf.copy_to_writer(self.writer)
273    }
274}
275
276#[test]
277fn test_lazy_bytes_deserialize() {
278    let mut lazy = Lazy::<Bytes>::default();
279    let bytes = Bytes::from_static(b"asdfasdf");
280    lazy.deserialize(&mut Cursor::new(bytes.clone()))
281        .expect("failed to deserialize");
282    let deserialized = lazy.into_buffer();
283    assert_eq!(deserialized, bytes, "The entire buffer should be copied");
284    assert_eq!(
285        deserialized.as_ptr(),
286        bytes.as_ptr(),
287        "The Bytes instance should be cloned"
288    )
289}