Rust Serde嵌套含大数组结构体反序列化栈溢出解决方法
问题根因
触发栈溢出的核心原因是Foo类型的栈尺寸过大:长度30000的bool数组会占用近30KB栈空间。serde默认派生的反序列化逻辑会在栈上生成待构造值的临时副本,单独反序列化Foo时栈上的大数组副本少,不会超过栈阈值;但嵌套在Bar中反序列化时,多份大数组临时值叠加,很容易打满测试线程的默认栈空间(常规环境下测试线程栈大小仅512KB~2MB),最终触发栈溢出崩溃。
解决方案
优先选择堆分配存储大数组的根治方案,不需要调整运行时配置,在任何环境下都不会出现同类栈溢出问题:
将Foo中的大数组用Box包裹,把数组的实际内存挪到堆上,此时Foo类型本身仅占一个指针的大小(约8字节),从根源上消除栈上大内存占用。修改后的可运行代码如下:
use serde_big_array::BigArray; use serde_derive::{Deserialize, Serialize}; pub const SIZE: usize = 30000; #[derive(Clone, Debug, Serialize, Deserialize)] struct Foo { #[serde(with = "BigArray")] pub vals: Box<[bool; SIZE]>, } #[derive(Clone, Debug, Serialize, Deserialize)] struct Bar { field0: Foo, } #[cfg(test)] mod tests { use super::*; #[test] fn deserialization_attempt_test() { let foo = Foo { vals: Box::new([false; SIZE]), }; let instance: Bar = Bar { field0: foo }; let json = serde_json::to_string(&instance).unwrap(); println!("PMD0"); let s_back = serde_json::from_str::<Bar>(&json).unwrap(); println!("PMD1"); } }
修改后访问数组元素的方式和原有逻辑完全一致,序列化生成的JSON格式也没有变化,不需要调整其他业务代码。
如果你暂时不想修改Foo的类型定义,可以临时通过增大线程最小栈空间的方式运行测试,但该方案治标不治本,程序在栈空间更小的环境运行时仍可能触发崩溃:
RUST_MIN_STACK=8388608 cargo t deserialization_attempt_test -- --nocapture
上述命令将测试线程最小栈设置为8MB,足够容纳多份大数组临时副本。
内容的提问来源于stack exchange,提问作者Thorkil Værge
相关产品推荐
相关产品推荐

