按需(惰性)输入

如果你可以轻松地预先提供所有输入,则 Salsa 输入工作得最好。然而,有时输入集是事先不知道的。

一个典型的例子是从磁盘读取文件。虽然 Salsa 可以急切地扫描特定目录并把内存中创建的文件树作为输入结构体,但更直接的方法是延迟读取文件。

也就是说,当某个查询第一次请求文件的文本时:

  1. 从磁盘读取文件并缓存它
  2. 为此路径设置文件系统监视器 (file-system watcher)
  3. 在监视器发送更改通知的时候,更新缓存的文件

这可以在 Salsa 中使用数据库中的缓存输入和增加数据库 trait 方法来从缓存中获得。

一个完整的可运行的文件监视示例见 lazy-input

#[salsa::input] struct File { path: PathBuf, #[return_ref] contents: String, } trait Db: salsa::DbWithJar<Jar> { fn input(&self, path: PathBuf) -> Result<File>; } #[salsa::db(Jar)] struct Database { storage: salsa::Storage<Self>, logs: Mutex<Vec<String>>, files: DashMap<PathBuf, File>, file_watcher: Mutex<Debouncer<RecommendedWatcher>>, } impl Database { fn new(tx: Sender<DebounceEventResult>) -> Self { let storage = Default::default(); Self { storage, logs: Default::default(), files: DashMap::new(), file_watcher: Mutex::new(new_debouncer(Duration::from_secs(1), None, tx).unwrap()), } } } impl Db for Database { fn input(&self, path: PathBuf) -> Result<File> { let path = path .canonicalize() .wrap_err_with(|| format!("Failed to read {}", path.display()))?; Ok(match self.files.entry(path.clone()) { // If the file already exists in our cache then just return it. Entry::Occupied(entry) => *entry.get(), // If we haven't read this file yet set up the watch, read the // contents, store it in the cache, and return it. Entry::Vacant(entry) => { // Set up the watch before reading the contents to try to avoid // race conditions. let watcher = &mut *self.file_watcher.lock().unwrap(); watcher .watcher() .watch(&path, RecursiveMode::NonRecursive) .unwrap(); let contents = std::fs::read_to_string(&path) .wrap_err_with(|| format!("Failed to read {}", path.display()))?; *entry.insert(File::new(self, path, contents)) } }) } }
  • Db trait 上定义一个按需获取 File 的方法(它只需要 &dyn Db 而不需要 &mut dyn Db
  • 每个文件应该只有一个输入结构体,所以给方法实现缓存(DashMap 就像 RwLock<HashMap> 一样)

然后,执行顶级查询的驱动代码负责在文件更改通知到达时更新文件内容。它更新 Salsa 输入的方式与更新任何其他输入的方式相同。

这里,我们实现了一个简单的驱动循环,每当文件发生变化时,它都会重新编译代码。你可以使用日志来检查是否只重新计算了可能已更改的查询。

{{#include ../../../examples-2022/lazy-input/src/main.rs:main}}