基础设施 4.0 · 优秀 2026-08-23 · 文章

A Syncthing and SQLite Gotcha

作者的 Rust 日记应用 Epoch(systemd 服务 + SQLite)经 Syncthing 在桌面/笔记本间同步数据库,出现怪 bug:笔记本写完等同步完成桌面打开当条记录文字消失;用 sqlite3 命令行看文件内容都在,只有重启服务才能读到新文本根因在 POSIX rename 的语义:Syncthing 用 rename 原子替换文件,而 rename 只改路径inode的目录项映射不碰文件对象本身;持有旧文件描述符的进程继续读写那个已经没有路径指向的孤儿 inode,直到所有 fd 释放它才真正消失作者由此把路径即文件的心智模型纠正为目录项 vs 文件对象双层模型

打开原文回到归档

A Syncthing and SQLite Gotcha

中文导读

作者的 Rust 日记应用 Epoch(systemd 服务 + SQLite)经 Syncthing 在桌面/笔记本间同步数据库,出现怪 bug:笔记本写完、等同步完成、桌面打开当条记录文字消失;用 sqlite3 命令行看文件内容都在,只有重启服务才能读到新文本。根因在 POSIX rename 的语义:Syncthing 用 rename 原子替换文件,而 rename 只改“路径→inode”的目录项映射、不碰文件对象本身;持有旧文件描述符的进程继续读写那个已经没有路径指向的孤儿 inode,直到所有 fd 释放它才真正消失。作者由此把“路径即文件”的心智模型纠正为“目录项 vs 文件对象”双层模型。

为什么值得关注

对所有“在本地文件上做同步/原子替换”的系统(Syncthing 类文件同步、配置热更新、数据库文件搬运)都有直接借鉴价值;它解释了一类“文件明明更新了、长驻进程却读不到”的幽灵 bug,并给出正确的心智模型而非 workaround。

关键信息

  • 场景:Rust + rusqlite 的 systemd 服务,SQLite 文件经 Syncthing 双机同步
  • 症状:新文本在文件里、服务读不到;重启服务后恢复
  • 根因:POSIX rename 只改目录项→inode 映射;已打开的 fd 继续指向旧 inode(无路径的孤儿文件)
  • 所有 fd 释放前,旧 inode 不会消失
  • 正确心智模型:目录项与文件对象是两层,rename 操作目录项层

English Summary

A Rust journal app (systemd service + SQLite) synced via Syncthing showed entries invisible on the desktop until service restart, though sqlite3 CLI could see the new text. Root cause: POSIX rename swaps the directory entry, not the file object; processes with open descriptors keep using the orphaned old inode until every descriptor is released. Directory entries and file objects are two layers.

原文要点摘录

those processes can keep reading and writing to the old file object, but the file is orphaned in that no path points to it. And once all file descriptors are released, the file becomes inaccessible.
rename works at the level of directory entries: it atomically mutates the mapping from pathnames to files but doesn't touch files at all.

Obsidian Notes

  • 内容由 opencli web read 抓取 borretti.me 原文生成(2026-08-23)。
  • 候选来源:AK-RSS Digest 2026-08-23(评分 7.6);机制描述锚定原文对 rename 语义的两段解释。