# Ledger lock recovery / 账本锁恢复 ## English ### Configuration recovery after an abnormal shutdown The ledger and its independent `ledger.json.config-bak` are written to temporary files in the same directory, flushed, and then renamed over their destinations. POSIX systems also flush the directory after replacement. The backup stores only configuration and migration markers, using the ledger's existing secret filtering; credentials remain in the DSH credential store and configuration placeholders are preserved. Usage-only updates do not rewrite an unchanged backup. A healthy existing ledger creates or updates this backup when loaded. If the ledger is corrupt, the original `.corrupt-*` file is retained and settings are recovered from the backup. A missing ledger can also use the backup. The log reports successful restoration and the number of custom balance sources; an unreadable backup is reported and defaults are used. Usage history continues through the existing backfill mechanism. An already zeroed file from before this protection, with no backup, contains no settings to recover. See [#412](https://github.com/Han-1413141/dsh-cost-meter/issues/412). Issue [#237](https://github.com/Han-1413141/dsh-cost-meter/issues/237) reports a startup failure with **Ledger is busy; preserving pending changes for retry**. The sidebar balance and quota widgets disappear because the cost-meter loader entry did not activate. An old lock can name a PID that now belongs to a different process; checking only whether that PID exists cannot identify the original owner. ### Update and recover 1. Update `dsh-cost-meter` in the affected Profile. Web users can run `dsh plugin --profile web add dsh-cost-meter`. Desktop users must use the application's own CLI and the `desktop` Profile; see the [Desktop guide](install-troubleshooting.md#desktop-安装与更新). 2. Restart that DSH host. For Desktop, fully exit the application including its tray process, then reopen it. The upgraded plugin recovers a proven stale lock before loading its ledger. 3. Confirm that the host logs contain `[dsh-cost-meter] 已加载` and that the Cost page and sidebar widgets return. Existing ledger amounts and history remain intact; this recovery does not reprice usage. A previously failed loader entry is not necessarily retried when an unrelated configuration file changes. Restarting the affected host applies the installed fix; adding a dummy `balance.refreshMinutes` override is unnecessary. This plugin does not change DSH's loader/HMR retry policy. ### What changes - Owner filenames retain their original `PID-UUID` format. The owner file now records process identity: Linux uses the boot ID, PID namespace and process start ticks; Windows uses the native process creation time. - A lock is recovered when its process has exited, or when a comparable native identity confirms PID reuse. Linux locks with identity records from another host or PID namespace are not treated as local owners. - Earlier versions wrote empty owner files. These are recovered only when the file predates the identified process's start, with a two-second margin for timestamp precision. The current process also has an uptime-based fallback. A lock is not expired just because it is old. - Unknown ownership, inaccessible process information and incomplete owner writes remain locked. A genuine competing writer retains its lock. Only the identified owner file and empty lock directory are removed; the ledger is preserved. If a genuine writer still holds the lock, let its operation finish and restart the failed host entry. For persistent failures, attach the plugin and DSH versions, other installed plugins, the sanitized startup error, and lock directory filenames/timestamps. Do not include the ledger contents or credentials. Do not delete `ledger.json` to restore the widgets. Implementation references: [Linux process start ticks](https://man7.org/linux/man-pages/man5/proc_pid_stat.5.html), [Windows process StartTime](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process.starttime). ## 简体中文 ### 异常退出后的配置恢复 账本及独立的 `ledger.json.config-bak` 均先写入同目录临时文件,刷盘后再替换目标文件;POSIX 系统同时同步目录。备份只保存配置和迁移记录,沿用账本的脱敏规则,凭据仍由 DSH 凭据库保存,配置中的占位符保留。仅用量变化时不重写配置备份。 已有的正常账本加载时即建立或更新备份。账本损坏后保留原始 `.corrupt-*` 文件,并从备份恢复设置;账本缺失时同样读取备份。日志显示恢复结果及自定义余额源数量;备份也无法读取时记录错误并使用默认配置。用量历史继续由现有补录流程处理。启用此保护之前已经全零且没有备份的文件,没有可恢复的配置内容。问题来源:[#412](https://github.com/Han-1413141/dsh-cost-meter/issues/412)。 Issue [#237](https://github.com/Han-1413141/dsh-cost-meter/issues/237) 报告启动时出现 **Ledger is busy; preserving pending changes for retry**。费用插件未成功挂载,侧栏余额和额度卡片随之消失。旧锁中的 PID 可能已经被系统分配给另一个进程,仅检查 PID 是否存在不能确认原锁持有者。 ### 升级与恢复 1. 更新受影响 Profile 中的 `dsh-cost-meter`。Web 用户可执行 `dsh plugin --profile web add dsh-cost-meter`。桌面端使用应用自带 CLI 和 `desktop` Profile,见 [Desktop 安装说明](install-troubleshooting.md#desktop-安装与更新)。 2. 重启该 DSH 宿主。桌面端完全退出应用(包括托盘)后重新打开。新版插件会在读取账本前回收已确认失效的锁。 3. 确认日志出现 `[dsh-cost-meter] 已加载`,费用页和侧栏卡片恢复。已有金额和历史记录保留,此过程不重新定价。 已经失败的加载器条目不一定会因无关配置文件变化而重试。重启受影响宿主即可加载已安装修复,无需添加临时 `balance.refreshMinutes` 配置。本插件不修改 DSH 加载器的热重载重试策略。 ### 修复规则 - 锁文件名继续使用原有 `PID-UUID` 格式,文件内容增加进程身份。Linux 保存系统启动 ID、PID 命名空间和进程启动计数;Windows 保存操作系统提供的进程创建时间。 - 进程已经退出,或原生启动身份能确认 PID 被复用时,回收旧锁。对于有身份记录的 Linux 锁,不会把其他主机或 PID 命名空间中的持有者误当成本机进程。 - 兼容旧版空锁文件。只有文件早于对应进程启动时间,并留出两秒精度余量时,才认定失效;当前进程还可用运行时长推算启动时间。锁的存放时间长短不是独立回收依据。 - 身份不明、进程信息不可读取或文件未写完整时,保留锁。真实并发写入者继续持锁。恢复只删除已确认的锁持有者文件和空锁目录,保留账本。 如果是真实写入进程占锁,等待其操作结束后重启加载失败的宿主。持续失败时,请附插件和 DSH 版本、其他已安装插件、脱敏启动错误,以及锁目录中文件的名称和时间。不要附账本内容或凭据,也不要通过删除 `ledger.json` 恢复卡片。