Blog
网站首页
用Tera实现递归import文件
用Tera实现递归import文件
2024-05-15 11:57
2024-05-15 13:39
作者:
xmh0511
提交
```rust use std::{ collections::HashMap, sync::{ atomic::{AtomicU16, Ordering}, Arc, RwLock, Weak, }, }; use tera::{Context, Function, Tera, Value}; struct S { level: AtomicU16, root: Arc
, tera: Weak
>>, } struct Defer
(F); impl
Defer
{ fn new(f: F) -> Self { Defer(f) } } impl
Drop for Defer
{ fn drop(&mut self) { (self.0)(); } } impl Function for S { fn call(&self, args: &HashMap
) -> tera::Result
{ if let Some(path) = args.get("path") { let path = path.as_str().ok_or(tera::Error::msg("invalid path"))?; let mut context = self.root.as_ref().clone().into_json(); let old_level = self.level.fetch_add(1, Ordering::Relaxed); let _guard = Defer::new(|| { self.level.store(old_level, Ordering::Relaxed); }); println!("old_level = {}", old_level); for _ in 0..old_level { let mut ctx = Context::new(); ctx.insert("Parent", &context); context = ctx.into_json(); } let context = Context::from_value(context)?; let s = self .tera .upgrade() .unwrap() .read() .unwrap() .as_ref() .unwrap() .render(path, &context)?; return Ok(tera::Value::String(s)); } Err(tera::Error::msg("no path")) } } impl Drop for S { fn drop(&mut self) { println!("drop S"); } } fn main() { let mut tera = Tera::new("templates/**/*").unwrap(); let mut context = Context::new(); context.insert("abc", &1); let context2 = context.clone(); let r_ptr = Arc::new(RwLock::new(None)); tera.register_function( "import", S { level: AtomicU16::new(1), tera: Arc::downgrade(&r_ptr), root: Arc::new(context2), }, ); { *r_ptr.write().unwrap() = Some(tera.clone()); // println!( // "strong_count {}, weak_count {}", // Arc::strong_count(&r_ptr), // Arc::weak_count(&r_ptr) // ); } let r = tera.render("a.html", &context).unwrap(); println!("{r}"); } ``` 如果`S::tera`字段是`Arc
>>`就会出现循环引用导致资源泄漏问题。为分析简单,`Arc
>>`可以等价于`Arc
`, 形成如下的引用关系图:  销毁变量`tera`会销毁`Functions`,其类型为`HashMap
>`, 从而调用`Arc
::drop`, 因为此时`S`有两个所有者,因此`S_owner_1`的销毁并不会销毁`S`, 当销毁`r_ptr`时,因为`cloned_tera`有两个所有者,因此`r_ptr`的销毁不会销毁触发`cloned_tera`的销毁,因此资源`S`和`cloned_tera`以及`S_owner_2`都存在泄露。 而示例中使用了`Weak
>>`替代`Arc
>>`, 依然为了简化讨论,前者可以等同于`Weak
`,形成的引用关系图如下:  `tera`的销毁触发`Functions`的销毁,从而触发`S_owner_1`的销毁,因为此时`S`有两个所有者,因此`S_owner_1`的销毁并不会导致`S`销毁,但之后`S`的所有者只有`S_owner_2`, 而`r_ptr`的销毁会导致`cloned_tera`销毁,因为`cloned_tera`的唯一所有者就是`r_ptr`, 而`cloned_tera`的销毁会触发其中的`Functions`的销毁进而触发`S_owner_2`的销毁,因为此时`S`的所有者就是`S_owner_2`,因此会触发`S`的销毁。