Skip to content

Commit c1e0887

Browse files
authored
fix: avoid throwing in tryRecoverDBFiles, decouple and cleanup persistence (#285)
1 parent 8e718c4 commit c1e0887

File tree

3 files changed

+400
-361
lines changed

3 files changed

+400
-361
lines changed

src/lib/db.test.ts

Lines changed: 105 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ jest.mock("fs-extra", () => {
4242
}),
4343
};
4444
});
45-
4645
function assertEqual<
4746
T1 extends {
4847
keys(): IterableIterator<string>;
@@ -108,6 +107,7 @@ describe("lib/db", () => {
108107
).toThrowError("intervalMinChanges");
109108
});
110109
});
110+
111111
describe("validates throttleFS options", () => {
112112
it("intervalMs < 0", () => {
113113
expect(
@@ -141,24 +141,33 @@ describe("lib/db", () => {
141141
testFS = new TestFS();
142142
testFSRoot = await testFS.getRoot();
143143

144-
await testFS.create({
145-
yes:
146-
// Final newline omitted on purpose
147-
'{"k":"key1","v":1}\n{"k":"key2","v":"2"}\n{"k":"key1"}',
148-
emptyLines:
149-
'\n{"k":"key1","v":1}\n\n\n{"k":"key2","v":"2"}\n\n',
150-
broken: `{"k":"key1","v":1}\n{"k":,"v":1}\n`,
151-
broken2: `{"k":"key1","v":1}\n{"k":"key2","v":}\n`,
152-
broken3: `{"k":"key1"\n`,
153-
reviver: `
144+
try {
145+
await testFS.remove();
146+
await testFS.create({
147+
yes:
148+
// Final newline omitted on purpose
149+
'{"k":"key1","v":1}\n{"k":"key2","v":"2"}\n{"k":"key1"}',
150+
emptyLines:
151+
'\n{"k":"key1","v":1}\n\n\n{"k":"key2","v":"2"}\n\n',
152+
broken: `{"k":"key1","v":1}\n{"k":,"v":1}\n`,
153+
broken2: `{"k":"key1","v":1}\n{"k":"key2","v":}\n`,
154+
broken3: `{"k":"key1"\n`,
155+
reviver: `
154156
{"k":"key1","v":1}
155157
{"k":"key2","v":"2"}
156158
{"k":"key1"}
157159
{"k":"key1","v":true}`,
158-
});
160+
});
161+
} catch (e) {
162+
debugger;
163+
}
159164
});
160165
afterEach(async () => {
161-
await testFS.remove();
166+
try {
167+
await testFS.remove();
168+
} catch (e) {
169+
debugger;
170+
}
162171
});
163172

164173
it("sets the isOpen property to true", async () => {
@@ -769,11 +778,11 @@ describe("lib/db", () => {
769778

770779
it("does not do anything while the DB is being closed", async () => {
771780
db.set("key3", 3);
772-
await db.compress(); // this writes the DB
781+
await wait(30);
773782
db.delete("key2");
774783
db.set("key3", 3.5);
775-
const closePromise = db.close(); // this only appends the extra key3 line
776-
await db.compress(); // this does not compress
784+
const closePromise = db.close();
785+
await db.compress();
777786
await closePromise;
778787

779788
await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
@@ -872,8 +881,6 @@ describe("lib/db", () => {
872881
afterEach(async () => {
873882
await db.close();
874883
await testFS.remove();
875-
mockMoveFileThrottle = 0;
876-
mockAppendFileThrottle = 0;
877884
});
878885

879886
it("does not crash", async () => {
@@ -922,28 +929,36 @@ describe("lib/db", () => {
922929
expect(db.uncompressedSize).toBe(7);
923930
});
924931

925-
it("increases by 1 for each set command", async () => {
932+
it("increases by 1 for each persisted set command", async () => {
926933
db.set("key4", 1);
934+
await wait(50);
927935
expect(db.uncompressedSize).toBe(8);
928936
db.set("key4", 1);
937+
await wait(50);
929938
expect(db.uncompressedSize).toBe(9);
930939
db.set("key4", 1);
940+
await wait(50);
931941
expect(db.uncompressedSize).toBe(10);
932942
db.set("key5", 2);
943+
await wait(50);
933944
expect(db.uncompressedSize).toBe(11);
934945
});
935946

936-
it("increases by 1 for each non-noop delete", async () => {
947+
it("increases by 1 for each persisted delete", async () => {
937948
db.delete("key4");
949+
await wait(50);
938950
expect(db.uncompressedSize).toBe(7);
939951
db.delete("key2");
952+
await wait(50);
940953
expect(db.uncompressedSize).toBe(8);
941954
db.delete("key2");
955+
await wait(50);
942956
expect(db.uncompressedSize).toBe(8);
943957
});
944958

945-
it("is reset to 0 after clear()", async () => {
959+
it("is reset to 0 after clear() is persisted", async () => {
946960
db.clear();
961+
await wait(100);
947962
expect(db.uncompressedSize).toBe(0);
948963
});
949964

@@ -968,12 +983,11 @@ describe("lib/db", () => {
968983
describe("auto-compression", () => {
969984
const testFilename = "autoCompress.jsonl";
970985
let testFilenameFull: string;
971-
const uncompressed = `
972-
{"k":"key1","v":1}
986+
const uncompressed = `{"k":"key1","v":1}
973987
{"k":"key2","v":"2"}
974988
{"k":"key3","v":3}
975989
{"k":"key2"}
976-
{"k":"key3","v":3.5}`;
990+
{"k":"key3","v":3.5}\n`;
977991

978992
let db: JsonlDB;
979993
let testFS: TestFS;
@@ -984,7 +998,7 @@ describe("lib/db", () => {
984998
testFSRoot = await testFS.getRoot();
985999
testFilenameFull = path.join(testFSRoot, testFilename);
9861000
await testFS.create({
987-
[testFilename]: `{"k":"key1","v":1}`,
1001+
[testFilename]: `{"k":"key1","v":1}\n`,
9881002
openClose: uncompressed,
9891003
});
9901004
});
@@ -999,43 +1013,58 @@ describe("lib/db", () => {
9991013
sizeFactor: 4,
10001014
},
10011015
});
1002-
const compressSpy = jest.spyOn(db, "compress");
10031016
await db.open();
10041017

1005-
for (let i = 2; i <= 9; i++) {
1006-
db.set("key1", i);
1007-
// compress is async, so give it some time
1008-
await wait(20);
1009-
if (i <= 3) {
1010-
expect(compressSpy).not.toBeCalled();
1011-
} else if (i <= 6) {
1012-
expect(compressSpy).toBeCalledTimes(1);
1013-
} else {
1014-
expect(compressSpy).toBeCalledTimes(2);
1015-
}
1016-
}
1018+
db.set("key1", 2);
1019+
await wait(25);
1020+
db.set("key1", 3);
1021+
await wait(25);
1022+
1023+
await expect(
1024+
fs.readFile(testFilenameFull, "utf8"),
1025+
).resolves.not.toBe('{"k":"key1","v":3}\n');
1026+
1027+
db.set("key1", 4);
1028+
// compress is async, so give it some time
1029+
await wait(100);
1030+
1031+
await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
1032+
'{"k":"key1","v":4}\n',
1033+
);
10171034

10181035
await db.close();
10191036
});
10201037

10211038
it("..., but only above the minimum size", async () => {
1039+
jest.retryTimes(5);
1040+
10221041
db = new JsonlDB(testFilenameFull, {
10231042
autoCompress: {
10241043
sizeFactor: 4,
1025-
sizeFactorMinimumSize: 20,
1044+
sizeFactorMinimumSize: 6,
10261045
},
10271046
});
1028-
const compressSpy = jest.spyOn(db, "compress");
10291047
await db.open();
10301048

1031-
for (let i = 2; i <= 20; i++) {
1049+
for (let i = 2; i <= 5; i++) {
10321050
db.set("key1", i);
1033-
// compress is async, so give it some time
1034-
await wait(20);
1051+
await wait(75);
10351052
}
1053+
1054+
await expect(
1055+
fs.readFile(testFilenameFull, "utf8"),
1056+
).resolves.not.toBe('{"k":"key1","v":5}\n');
1057+
1058+
db.set("key1", 6);
1059+
// Wait a bit because compress is async
1060+
await wait(50);
1061+
// close the DB to make sure everything is flushed
10361062
await db.close();
1037-
expect(compressSpy).toBeCalledTimes(1);
1038-
});
1063+
1064+
await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
1065+
'{"k":"key1","v":6}\n',
1066+
);
1067+
}, 10000);
10391068

10401069
it("doesn't trigger when different keys are added", async () => {
10411070
db = new JsonlDB(testFilenameFull, {
@@ -1061,16 +1090,22 @@ describe("lib/db", () => {
10611090
intervalMs: 100,
10621091
},
10631092
});
1064-
const compressSpy = jest.spyOn(db, "compress");
10651093
await db.open();
10661094

1067-
for (let i = 1; i <= 3; i++) {
1068-
// Don't test too often, or we'll run into a mismatch
1069-
db.set("key1", i);
1070-
// compress is async, so give it some time
1071-
await wait(110);
1072-
expect(compressSpy).toBeCalledTimes(i);
1073-
}
1095+
db.set("key1", 2);
1096+
await wait(25);
1097+
db.set("key1", 3);
1098+
await wait(25);
1099+
1100+
await expect(
1101+
fs.readFile(testFilenameFull, "utf8"),
1102+
).resolves.not.toBe('{"k":"key1","v":3}\n');
1103+
1104+
await wait(75);
1105+
1106+
await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
1107+
'{"k":"key1","v":3}\n',
1108+
);
10741109

10751110
await db.close();
10761111
});
@@ -1082,19 +1117,19 @@ describe("lib/db", () => {
10821117
intervalMinChanges: 2,
10831118
},
10841119
});
1085-
const compressSpy = jest.spyOn(db, "compress");
10861120
await db.open();
10871121

1088-
await wait(100);
1089-
expect(compressSpy).not.toBeCalled();
1090-
1091-
db.set("key1", 1);
1092-
await wait(100);
1093-
expect(compressSpy).not.toBeCalled(); // only 1 change
1122+
db.set("key1", 2);
1123+
await wait(110);
1124+
await expect(
1125+
fs.readFile(testFilenameFull, "utf8"),
1126+
).resolves.not.toBe('{"k":"key1","v":2}\n');
10941127

1095-
db.set("key1", 1);
1096-
await wait(100);
1097-
expect(compressSpy).toBeCalledTimes(1); // two changes
1128+
db.set("key1", 3);
1129+
await wait(110);
1130+
await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
1131+
'{"k":"key1","v":3}\n',
1132+
);
10981133

10991134
await db.close();
11001135
});
@@ -1110,6 +1145,7 @@ describe("lib/db", () => {
11101145
// Cannot use this, since open calls compressInternal
11111146
// expect(compressSpy).toBeCalledTimes(1);
11121147
await db.open();
1148+
await wait(25);
11131149
await expect(fs.readFile(testFilenameFull, "utf8")).resolves.toBe(
11141150
'{"k":"key1","v":1}\n{"k":"key3","v":3.5}\n',
11151151
);
@@ -1178,12 +1214,14 @@ describe("lib/db", () => {
11781214
},
11791215
});
11801216
await db.open();
1217+
db.clear();
11811218

11821219
// Trigger at least one scheduled cork before the first write
11831220
await wait(110);
11841221
await assertFileContent("");
11851222

11861223
db.set("1", 1);
1224+
11871225
let expected = `{"k":"1","v":1}\n`;
11881226
await assertFileContent("");
11891227

@@ -1222,7 +1260,7 @@ describe("lib/db", () => {
12221260
}
12231261

12241262
// Give it a little time to write
1225-
await wait(10);
1263+
await wait(40);
12261264
await assertFileContent(expected);
12271265
});
12281266

@@ -1236,20 +1274,21 @@ describe("lib/db", () => {
12361274
});
12371275
await db.open();
12381276
await db.compress();
1277+
await wait(15);
12391278

12401279
db.set("1", 1);
12411280
let expected = `{"k":"1","v":1}\n`;
12421281
await assertFileContent("");
12431282

1244-
await wait(50);
1283+
await wait(15);
12451284
for (let i = 2; i <= 100; i++) {
12461285
db.set(i.toString(), i);
12471286
await assertFileContent("");
12481287
expected += `{"k":"${i}","v":${i}}\n`;
12491288
}
12501289

12511290
// Give it a little more time than necessary
1252-
await wait(60);
1291+
await wait(100);
12531292
await assertFileContent(expected);
12541293
});
12551294

0 commit comments

Comments
 (0)