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. The in-tree users are the extension-envelope payloads —
134/// `InterDaemonEvent::ExtensionMessage`, `DaemonRequest::ExtensionRequest`,
135/// and `DaemonReply::ExtensionReply` — and, carried opaquely inside one of
136/// those envelopes, the tensor-pool `PeerMessage::Write { tensor_data }`
137/// field (`dora_tensor_pool::protocol`), whose receiver copies the bytes
138/// into a mirror segment rather than decoding them zero-copy.
139/// (`InterDaemonEvent` has no `MemoryPoolWrite` variant, and the tensor-pool
140/// type is `PeerMessage::Write`, not `MemoryPoolWrite`.) The wire encoding is
141/// identical to serde's default `Vec<u8>` (postcard: varint length + raw
142/// bytes), so this is a pure speedup with no version bump —
143/// `vec_encoding_is_unchanged_from_the_seq_form` pins it.
144pub mod vec {
145    use serde::{
146        Deserializer, Serializer,
147        de::{self, Visitor},
148    };
149    use std::fmt;
150
151    pub fn serialize<S: Serializer>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
152        serializer.serialize_bytes(value)
153    }
154
155    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
156        deserializer.deserialize_bytes(VecPayloadVisitor)
157    }
158
159    struct VecPayloadVisitor;
160
161    impl<'de> Visitor<'de> for VecPayloadVisitor {
162        type Value = Vec<u8>;
163
164        fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165            f.write_str("a byte array")
166        }
167
168        fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
169            Ok(v.to_vec())
170        }
171
172        fn visit_borrowed_bytes<E: de::Error>(self, v: &'de [u8]) -> Result<Self::Value, E> {
173            Ok(v.to_vec())
174        }
175
176        fn visit_byte_buf<E: de::Error>(self, v: Vec<u8>) -> Result<Self::Value, E> {
177            Ok(v)
178        }
179
180        /// Self-describing formats (notably JSON) render bytes as a sequence of
181        /// numbers and hand that back here rather than to `visit_bytes`.
182        fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
183            let mut out = Vec::with_capacity(seq.size_hint().unwrap_or(0));
184            while let Some(byte) = seq.next_element::<u8>()? {
185                out.push(byte);
186            }
187            Ok(out)
188        }
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195    use serde::{Deserialize, Serialize};
196
197    /// Mirrors `DataMessage`'s shape but keeps serde's default (sequence)
198    /// encoding, so the two can be compared byte for byte.
199    #[derive(Serialize, Deserialize)]
200    struct SeqForm(Payload);
201
202    #[derive(Serialize, Deserialize)]
203    struct BytesForm(#[serde(with = "super")] Payload);
204
205    fn payload(len: usize) -> Payload {
206        let data: Vec<u8> = (0..len).map(|i| (i % 251) as u8).collect();
207        AVec::from_slice(ALIGN, &data)
208    }
209
210    /// The whole premise of this module: swapping the element loop for a bulk
211    /// copy must not move a single byte on the wire. Lengths straddle postcard's
212    /// varint length-prefix boundary at 127/128, where a naive change would
213    /// shift the prefix width.
214    #[test]
215    fn encoding_is_unchanged_from_the_seq_form() {
216        for len in [0, 1, 127, 128, 129, 300, 4096, 70_000] {
217            let value = payload(len);
218            assert_eq!(
219                postcard::to_stdvec(&SeqForm(value.clone())).expect("seq"),
220                postcard::to_stdvec(&BytesForm(value)).expect("bytes"),
221                "len {len}: bulk encoding differs from the sequence encoding — \
222                 this would be an unversioned wire break"
223            );
224        }
225    }
226
227    #[test]
228    fn round_trips_and_preserves_alignment() {
229        for len in [0, 1, 128, 4096] {
230            let value = payload(len);
231            let bytes = postcard::to_stdvec(&BytesForm(value.clone())).expect("serialize");
232            let back: BytesForm = postcard::from_bytes(&bytes).expect("deserialize");
233
234            assert_eq!(&back.0[..], &value[..], "len {len}: payload changed");
235            assert_eq!(
236                back.0.as_ptr() as usize % ALIGN,
237                0,
238                "len {len}: decoded payload must stay {ALIGN}-byte aligned for \
239                 the Arrow zero-copy path"
240            );
241        }
242    }
243
244    /// JSON hands bytes back as a sequence, so the visitor must accept that
245    /// shape too — `NodeConfig` and the WS planes travel as JSON.
246    #[test]
247    fn survives_a_json_round_trip() {
248        let value = payload(300);
249        let json = serde_json::to_string(&BytesForm(value.clone())).expect("to json");
250        let back: BytesForm = serde_json::from_str(&json).expect("from json");
251        assert_eq!(&back.0[..], &value[..]);
252
253        // And the JSON text itself is unchanged from the sequence form.
254        assert_eq!(
255            json,
256            serde_json::to_string(&SeqForm(value)).expect("seq to json")
257        );
258    }
259
260    #[test]
261    fn option_round_trips_in_both_states() {
262        #[derive(Serialize, Deserialize)]
263        struct OptForm(#[serde(with = "super::option")] Option<Payload>);
264        #[derive(Serialize, Deserialize)]
265        struct OptSeqForm(Option<Payload>);
266
267        for value in [None, Some(payload(0)), Some(payload(300))] {
268            let bytes = postcard::to_stdvec(&OptForm(value.clone())).expect("serialize");
269            assert_eq!(
270                bytes,
271                postcard::to_stdvec(&OptSeqForm(value.clone())).expect("seq"),
272                "option encoding differs from the sequence encoding"
273            );
274
275            let back: OptForm = postcard::from_bytes(&bytes).expect("deserialize");
276            match (&back.0, &value) {
277                (None, None) => {}
278                (Some(a), Some(b)) => assert_eq!(&a[..], &b[..]),
279                _ => panic!("option state changed across the round trip"),
280            }
281        }
282    }
283
284    // --- plain `Vec<u8>` variant (`crate::bulk_bytes::vec`) ---
285
286    #[derive(Serialize, Deserialize)]
287    struct VecSeqForm(Vec<u8>);
288
289    #[derive(Serialize, Deserialize)]
290    struct VecBytesForm(#[serde(with = "super::vec")] Vec<u8>);
291
292    fn vec_payload(len: usize) -> Vec<u8> {
293        (0..len).map(|i| (i % 251) as u8).collect()
294    }
295
296    /// Same premise as `encoding_is_unchanged_from_the_seq_form`, for the
297    /// plain-`Vec<u8>` helper used by the extension-envelope payloads and the
298    /// tensor-pool `PeerMessage::Write { tensor_data }` field (#3195):
299    /// swapping the per-element loop for a bulk copy must not move a byte on
300    /// the wire, including across postcard's 127/128 varint-prefix boundary.
301    #[test]
302    fn vec_encoding_is_unchanged_from_the_seq_form() {
303        for len in [0, 1, 127, 128, 129, 300, 4096, 70_000] {
304            let value = vec_payload(len);
305            assert_eq!(
306                postcard::to_stdvec(&VecSeqForm(value.clone())).expect("seq"),
307                postcard::to_stdvec(&VecBytesForm(value)).expect("bytes"),
308                "len {len}: bulk encoding differs from the sequence encoding — \
309                 this would be an unversioned wire break"
310            );
311        }
312    }
313
314    #[test]
315    fn vec_round_trips() {
316        for len in [0, 1, 128, 4096] {
317            let value = vec_payload(len);
318            let bytes = postcard::to_stdvec(&VecBytesForm(value.clone())).expect("serialize");
319            let back: VecBytesForm = postcard::from_bytes(&bytes).expect("deserialize");
320            assert_eq!(back.0, value, "len {len}: payload changed");
321        }
322    }
323
324    /// JSON hands bytes back as a sequence, so the visitor must accept that
325    /// shape too, and the JSON text must be unchanged from the sequence form.
326    #[test]
327    fn vec_survives_a_json_round_trip() {
328        let value = vec_payload(300);
329        let json = serde_json::to_string(&VecBytesForm(value.clone())).expect("to json");
330        let back: VecBytesForm = serde_json::from_str(&json).expect("from json");
331        assert_eq!(back.0, value);
332        assert_eq!(
333            json,
334            serde_json::to_string(&VecSeqForm(value)).expect("seq to json")
335        );
336    }
337}