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}