是否有标准的文件保存和交换模式

Is there a standard file save and swap pattern?

本文关键字:交换 模式 保存 文件 标准 是否      更新时间:2023-10-16

我们的产品在每个打开的文档文件上都有一个独占写入文件句柄,以确保我们对文件具有独占的写入控制。

因此,Windows 不允许任何其他进程执行比从文件读取更多的操作,也不能从资源管理器或其他进程中删除该文件 - 因为它上的打开(和写入独占)句柄。

但是,我们遇到了非常奇怪的边缘情况,即文件的内容已损坏。 我认为这与错误有关,或者可能是我对 Windows API 保证的内容的误解 - 即为了将设计文件保存在以前版本的顶部 - 我们目前持有文件句柄 - 我必须倒带句柄到文件的开头,写出来,然后强制它刷新并在新位置截断(以防文件缩小 - 我们不想要额外的我们文件末尾的污泥 - 这也是一种腐败形式)。 在会话期间多次执行此操作 - 每次用户编辑时都会保存其更改...

但是,有时我们的客户会报告所有这些导致损坏的文件(仅通过网络 - 从不在本地)。

我们认为这可能是因为我们的实际保存过程稍微复杂一些:

  1. 倒带(已打开的)文件句柄
  2. 写出核心数据
  3. 齐平并截断手柄
  4. 搜索到文件末尾
  5. 写出缩略图图像数据(本质上 - 附加缩略图)
  6. 齐平并截断手柄

这可能只是一个"不要刷新,搜索,写入,刷新"的情况 - 这在MS的网络文件系统代码中引入了微妙的错误(或依赖于系统内置的不确定性 - 并且不能可靠地依赖)?

因此,我正在实施两层修复:

  1. 执行单次倒带,然后写入核心数据 + 图像数据 + 刷新和截断(一次)
  2. 执行临时另存、关闭、重命名

No. 2 有一些不错的功能 - 例如"如果写出新文件时出现问题,旧文件保持不变。 这意味着在最坏的情况下,他们的新数据不会被保存,但没有旧数据丢失。

这是"构建一个新副本,然后将其交换为真实/活动数据结构"经典模式的基本用法。

很棒 - 但我不知道如何"交换文件内容"?

我可以做经典的:

  1. 完全写入 T(温度)并关闭它。
  2. 将 A(实际)文件重命名为 A.bak。
  3. 将 T 重命名为 A

(当然,我需要先删除任何以前的 A.bak)。

这很好 - 但同样 - 我们通常在 A 上有一个锁定的手柄。 所以这扩展到有点不完美:

  1. 写 T
  2. 关闭我们在 A 上的手柄
  3. 将 A 重命名为 A.bak
  4. 将 T 重命名为 A
  5. 获取 A 上的独占写入句柄

我不喜欢的是"活动部件太多"。

  • 在 2 到 5 之间,其他人都可以抓住 A 上的锁或以其他方式妨碍我们。

您不认为它会发生 - 但是文件系统索引或防病毒或备份软件都可能妨碍您,并且非常 - 非常 - 经常(根据我们的经验)。

所以 - 理想情况下,我不想在任何时候放弃对 A 的控制! 我想确保每次交接都不会受到防病毒软件或其他软件的影响,以免进入那里并解决问题。

理想情况下,事实上,我会:

  1. 写 T
  2. 交换 T 和 A 的内脏(要求文件系统实际将名称 A 链接到 T 的内容)
  3. 永远幸福地生活...

那么,有没有其他人发现的交换T和A的模式呢?

是否有一组 API 调用来使其更好/更健壮?

其他想法可能有助于重新思考我的方法?

注意:MS 已弃用事务文件系统 API。 所以这听起来像是一个非启动器 - 更不用说它无论如何都不适用于 Windows 下的所有文件系统。

更新: FWIW,我将其实现为写入临时文件,重命名原始文件,将临时重命名为真实文件,删除原始文件(加上必要的解锁并获得新锁)使用 RAII 和 ScopeGuard 来处理任何故障回滚,当然回滚 - 副作用和操作系统相关,是"最佳情况",不如C++语言合同本身得到很好的保证。 尽管如此,在测试期间它还是非常有效的 - 从来没有给我一个坏文件(我有意无意地创造了许多问题,这些问题创建了一个糟糕的临时文件,或者在调用展开过程的算法期间出现错误(抛出异常)。

更新 2:"> 最终"算法为
1。(保存到临时本地验证副本)
2. 保存到临时新文件
3.(验证新的保存和验证匹配)4. 将我们的锁放在真实文件
上 5. 将
真实文件重命名为临时旧文件,并将原始文件替换为临时文件(这包括传输属性、ACL 和时间戳 - 请参阅 ReplaceFile())
6. 获取我们的锁(如果它被锁定)
7.成功(抛弃我们的守卫)

ReplaceFile 是一种相当标准的方法。

它不能解决您的保持锁定问题,但它是您实现的测试版本,具有其他功能,例如将任何 ACL 从旧文件传输到新文件。

如果您保留自制实现,请确保处理Windows上的文件删除并不总是立即的情况。 例如,如果在将当前文件重命名为备份文件之前必须删除旧的"备份"文件,则重命名可能会失败。