Skip to main content

dora_message/
bulk_bytes.rs

1//! Bulk-payload serde for `AVec<u8, ConstAlign<128>>`.
2//!
3//! serde's default impl for a byte container is a *sequence* of `u8`, so the
4//! encoder writes the payload one element at a time. Routing it through
5//! `serialize_bytes`/`deserialize_bytes` instead lets the codec move the whole
6//! slice at once, which is 50–65x faster on payload-sized buffers.
7//!
8//! **The encoding is unchanged.** postcard writes a varint length followed by
9//! the raw bytes either way, so this is a pure speedup — no wire-format version
10//! bump. `encoding_is_unchanged_from_the_seq_form` pins that, including at the
11//! varint length-prefix boundaries. In self-describing formats the two forms
12//! also agree: serde_json renders both as an array of numbers, and the visitor
13//! below accepts that shape back via [`Visitor::visit_seq`].
14//!
15//! Deserialization preserves the 128-byte alignment the Arrow zero-copy decode
16//! path depends on — see `daemon_path_ipc_roundtrip_preserves_payload_and_alignment`.
17
18use std::fmt;
19
20use aligned_vec::{AVec, ConstAlign};
21use serde::{
22    Deserializer, Serializer,
23    de::{self, Visitor},
24};
25
26/// The alignment required by the Arrow zero-copy decode path.
27const ALIGN: usize = 128;
28
29type Payload = AVec<u8, ConstAlign<ALIGN>>;
30
31pub fn serialize<S: Serializer>(value: &Payload, serializer: S) -> Result<S::Ok, S::Error> {
32    serializer.serialize_bytes(value)
33}
34
35pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Payload, D::Error> {
36    deserializer.deserialize_bytes(PayloadVisitor)
37}
38
39struct PayloadVisitor;
40
41impl<'de> Visitor<'de> for PayloadVisitor {
42    type Value = Payload;
43
44    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        f.write_str("a byte array")
46    }
47
48    fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
49        Ok(AVec::from_slice(ALIGN, v))
50    }
51
52    fn visit_borrowed_bytes<E: de::Error>(self, v: &'de [u8]) -> Result<Self::Value, E> {
53        Ok(AVec::from_slice(ALIGN, v))
54    }
55
56    fn visit_byte_buf<E: de::Error>(self, v: Vec<u8>) -> Result<Self::Value, E> {
57        Ok(AVec::from_slice(ALIGN, &v))
58    }
59
60    /// Self-describing formats (notably JSON) render bytes as a sequence of
61    /// numbers and hand that back here rather than to `visit_bytes`.
62    fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
63        let mut out = AVec::with_capacity(ALIGN, seq.size_hint().unwrap_or(0));
64        while let Some(byte) = seq.next_element::<u8>()? {
65            out.push(byte);
66        }
67        Ok(out)
68    }
69}
70
71/// The same treatment for an `Option<AVec<..>>` field.
72pub mod option {
73    use super::{Payload, PayloadVisitor};
74    use serde::{
75        Deserializer, Serializer,
76        de::{self, Visitor},
77    };
78    use std::fmt;
79
80    pub fn serialize<S: Serializer>(
81        value: &Option<Payload>,
82        serializer: S,
83    ) -> Result<S::Ok, S::Error> {
84        match value {
85            Some(v) => serializer.serialize_some(&Wrapper(v)),
86            None => serializer.serialize_none(),
87        }
88    }
89
90    /// Applies [`super::serialize`] to the inner value; `serialize_some` needs
91    /// something implementing `Serialize`, and `#[serde(with)]` gives us a
92    /// free function rather than an impl.
93    struct Wrapper<'a>(&'a Payload);
94
95    impl serde::Serialize for Wrapper<'_> {
96        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
97            super::serialize(self.0, serializer)
98        }
99    }
100
101    pub fn deserialize<'de, D: Deserializer<'de>>(
102        deserializer: D,
103    ) -> Result<Option<Payload>, D::Error> {
104        deserializer.deserialize_option(OptionVisitor)
105    }
106
107    struct OptionVisitor;
108
109    impl<'de> Visitor<'de> for OptionVisitor {
110        type Value = Option<Payload>;
111
112        fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113            f.write_str("an optional byte array")
114        }
115
116        fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
117            Ok(None)
118        }
119
120        fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
121            Ok(None)
122        }
123
124        fn visit_some<D: Deserializer<'de>>(self, d: D) -> Result<Self::Value, D::Error> {
125            d.deserialize_bytes(PayloadVisitor).map(Some)
126        }
127    }
128}
129
130/// The same bulk-bytes treatment for a plain `Vec<u8>` field.
131///
132/// Used by cross-machine payloads that do not need the 128-byte Arrow
133/// alignment (`InterDaemonEvent::MemoryPoolWrite::tensor_data`), where the
134/// receiver copies the bytes into a mirror segment rather than decoding them
135/// zero-copy. The wire encoding is identical to serde's default `Vec<u8>`
136/// (postcard: varint length + raw bytes), so this is a pure speedup with no
137/// version bump — `vec_encoding_is_unchanged_from_the_seq_form` pins it.
138pub mod vec {
139    use serde::{
140        Deserializer, Serializer,
141        de::{self, Visitor},
142    };
143    use std::fmt;
144
145    pub fn serialize<S: Serializer>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
146        serializer.serialize_bytes(value)
147    }
148
149    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
150        deserializer.deserialize_bytes(VecPayloadVisitor)
151    }
152
153    struct VecPayloadVisitor;
154
155    impl<'de> Visitor<'de> for VecPayloadVisitor {
156        type Value = Vec<u8>;
157
158        fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159            f.write_str("a byte array")
160        }
161
162        fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
163            Ok(v.to_vec())
164        }
165
166        fn visit_borrowed_bytes<E: de::Error>(self, v: &'de [u8]) -> Result<Self::Value, E> {
167            Ok(v.to_vec())
168        }
169
170        fn visit_byte_buf<E: de::Error>(self, v: Vec<u8>) -> Result<Self::Value, E> {
171            Ok(v)
172        }
173
174        /// Self-describing formats (notably JSON) render bytes as a sequence of
175        /// numbers and hand that back here rather than to `visit_bytes`.
176        fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
177            let mut out = Vec::with_capacity(seq.size_hint().unwrap_or(0));
178            while let Some(byte) = seq.next_element::<u8>()? {
179                out.push(byte);
180            }
181            Ok(out)
182        }
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189    use serde::{Deserialize, Serialize};
190
191    /// Mirrors `DataMessage`'s shape but keeps serde's default (sequence)
192    /// encoding, so the two can be compared byte for byte.
193    #[derive(Serialize, Deserialize)]
194    struct SeqForm(Payload);
195
196    #[derive(Serialize, Deserialize)]
197    struct BytesForm(#[serde(with = "super")] Payload);
198
199    fn payload(len: usize) -> Payload {
200        let data: Vec<u8> = (0..len).map(|i| (i % 251) as u8).collect();
201        AVec::from_slice(ALIGN, &data)
202    }
203
204    /// The whole premise of this module: swapping the element loop for a bulk
205    /// copy must not move a single byte on the wire. Lengths straddle postcard's
206    /// varint length-prefix boundary at 127/128, where a naive change would
207    /// shift the prefix width.
208    #[test]
209    fn encoding_is_unchanged_from_the_seq_form() {
210        for len in [0, 1, 127, 128, 129, 300, 4096, 70_000] {
211            let value = payload(len);
212            assert_eq!(
213                postcard::to_stdvec(&SeqForm(value.clone())).expect("seq"),
214                postcard::to_stdvec(&BytesForm(value)).expect("bytes"),
215                "len {len}: bulk encoding differs from the sequence encoding — \
216                 this would be an unversioned wire break"
217            );
218        }
219    }
220
221    #[test]
222    fn round_trips_and_preserves_alignment() {
223        for len in [0, 1, 128, 4096] {
224            let value = payload(len);
225            let bytes = postcard::to_stdvec(&BytesForm(value.clone())).expect("serialize");
226            let back: BytesForm = postcard::from_bytes(&bytes).expect("deserialize");
227
228            assert_eq!(&back.0[..], &value[..], "len {len}: payload changed");
229            assert_eq!(
230                back.0.as_ptr() as usize % ALIGN,
231                0,
232                "len {len}: decoded payload must stay {ALIGN}-byte aligned for \
233                 the Arrow zero-copy path"
234            );
235        }
236    }
237
238    /// JSON hands bytes back as a sequence, so the visitor must accept that
239    /// shape too — `NodeConfig` and the WS planes travel as JSON.
240    #[test]
241    fn survives_a_json_round_trip() {
242        let value = payload(300);
243        let json = serde_json::to_string(&BytesForm(value.clone())).expect("to json");
244        let back: BytesForm = serde_json::from_str(&json).expect("from json");
245        assert_eq!(&back.0[..], &value[..]);
246
247        // And the JSON text itself is unchanged from the sequence form.
248        assert_eq!(
249            json,
250            serde_json::to_string(&SeqForm(value)).expect("seq to json")
251        );
252    }
253
254    #[test]
255    fn option_round_trips_in_both_states() {
256        #[derive(Serialize, Deserialize)]
257        struct OptForm(#[serde(with = "super::option")] Option<Payload>);
258        #[derive(Serialize, Deserialize)]
259        struct OptSeqForm(Option<Payload>);
260
261        for value in [None, Some(payload(0)), Some(payload(300))] {
262            let bytes = postcard::to_stdvec(&OptForm(value.clone())).expect("serialize");
263            assert_eq!(
264                bytes,
265                postcard::to_stdvec(&OptSeqForm(value.clone())).expect("seq"),
266                "option encoding differs from the sequence encoding"
267            );
268
269            let back: OptForm = postcard::from_bytes(&bytes).expect("deserialize");
270            match (&back.0, &value) {
271                (None, None) => {}
272                (Some(a), Some(b)) => assert_eq!(&a[..], &b[..]),
273                _ => panic!("option state changed across the round trip"),
274            }
275        }
276    }
277
278    // --- plain `Vec<u8>` variant (`crate::bulk_bytes::vec`) ---
279
280    #[derive(Serialize, Deserialize)]
281    struct VecSeqForm(Vec<u8>);
282
283    #[derive(Serialize, Deserialize)]
284    struct VecBytesForm(#[serde(with = "super::vec")] Vec<u8>);
285
286    fn vec_payload(len: usize) -> Vec<u8> {
287        (0..len).map(|i| (i % 251) as u8).collect()
288    }
289
290    /// Same premise as `encoding_is_unchanged_from_the_seq_form`, for the
291    /// plain-`Vec<u8>` helper used by `MemoryPoolWrite::tensor_data` (#3195):
292    /// swapping the per-element loop for a bulk copy must not move a byte on
293    /// the wire, including across postcard's 127/128 varint-prefix boundary.
294    #[test]
295    fn vec_encoding_is_unchanged_from_the_seq_form() {
296        for len in [0, 1, 127, 128, 129, 300, 4096, 70_000] {
297            let value = vec_payload(len);
298            assert_eq!(
299                postcard::to_stdvec(&VecSeqForm(value.clone())).expect("seq"),
300                postcard::to_stdvec(&VecBytesForm(value)).expect("bytes"),
301                "len {len}: bulk encoding differs from the sequence encoding — \
302                 this would be an unversioned wire break"
303            );
304        }
305    }
306
307    #[test]
308    fn vec_round_trips() {
309        for len in [0, 1, 128, 4096] {
310            let value = vec_payload(len);
311            let bytes = postcard::to_stdvec(&VecBytesForm(value.clone())).expect("serialize");
312            let back: VecBytesForm = postcard::from_bytes(&bytes).expect("deserialize");
313            assert_eq!(back.0, value, "len {len}: payload changed");
314        }
315    }
316
317    /// JSON hands bytes back as a sequence, so the visitor must accept that
318    /// shape too, and the JSON text must be unchanged from the sequence form.
319    #[test]
320    fn vec_survives_a_json_round_trip() {
321        let value = vec_payload(300);
322        let json = serde_json::to_string(&VecBytesForm(value.clone())).expect("to json");
323        let back: VecBytesForm = serde_json::from_str(&json).expect("from json");
324        assert_eq!(back.0, value);
325        assert_eq!(
326            json,
327            serde_json::to_string(&VecSeqForm(value)).expect("seq to json")
328        );
329    }
330}