7

解构赋值踩坑:神秘失踪的数据

 2 years ago
source link: https://blog.jiejiss.com/%E8%A7%A3%E6%9E%84%E8%B5%8B%E5%80%BC%E8%B8%A9%E5%9D%91%EF%BC%9A%E7%A5%9E%E7%A7%98%E5%A4%B1%E8%B8%AA%E7%9A%84%E6%95%B0%E6%8D%AE/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

解构赋值踩坑:神秘失踪的数据

2022-03-15

话说在我们 PLCT ArchRV 组,基础设施都是我们自己搭出来的,分工、打标记、追踪状态全部靠 XieJiSS/plct-archrv-pkg-botcubercsl/archrv-pkg-notification-botAst-x64/plct-archrv-status-worker 实现。近日我们在维护标记和追踪状态时,发现 plct-archrv-pkg-bot (以下简称 bot)在重启时会丢失几条记录。

最开始,怀疑是重启的时候还有数据没写入到磁盘,导致数据丢失。但是经过排查,发现并不太可能,理论上所有的数据都是在修改时当场写入磁盘的。保险起见,增加了使用同步 IO 的 storePackageMarksSync,通过 async-exit-hook 在进程退出时强制保存。然而还是丢数据。

看 log 可以确认,在退出时把内存里的数据全部写入到磁盘了,那丢数据是怎么回事呢?

又开始考虑是不是 race condition,于是通过 lockfile 实现了进程的保护锁,确保同时只有一个 bot 进程在运行。除了没效果以外,效果非常好。看来也不是多进程并行写入的问题。

最后经过排查,发现问题的根源在 exportimport 上。

最小可复现样例如下:

// a.js
let A = [1, 2, 3, null];

function cleanUp() {
A = A.filter(num => typeof num === "number");
}

function showData() {
console.log(A);
}

module.exports = { A, cleanUp, showData };
// b.js
const { A, cleanup, showData } = require("./a");

A.push(4);
cleanUp();
A.push(5);
A.push(6);
showData(); // 只有 1, 2, 3, 4

可以看到,在执行 cleanUp 之后的数据全部丢失了。这是因为在 a.js 里对 A 重新赋值过后,b.js 里的 Aa.js 里的 A 不再指向同一个对象。

但是,如果采用另一种写法:

// b.js
const a = require("./a");

a.A.push(4);
a.cleanUp();
a.A.push(5);
a.A.push(6);
a.showData(); // 1, 2, 3, 4, 5, 6

这是为什么呢?我们用 cpp 来翻译一下第一种写法就明白了。

const auto *A = a.A;
a.A = new vector<int>();
// 此时 A 和 a.A 显然已经不再指向同一块内存了

所概括一下:把 JS 里面解构赋值拿到的东西当做 &ref 的错误理解就会导致这种 inconsistency 的出现。

如果想修复这个问题,需要在 a.js 里面把 A 声明为 const,随后将 Array#filter 替换为自己实现的 in-place filter,详见这个 commit。当然你也可以在 b.js 里始终使用 a.A 的方式来访问 A

来源:https://blog.jiejiss.com/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK