@agoric/swing-store 0.9.2-dev-00f86bb.0 → 0.9.2-dev-914bee2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/transcriptStore.js +54 -39
- package/test/deletion.test.js +139 -125
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agoric/swing-store",
|
|
3
|
-
"version": "0.9.2-dev-
|
|
3
|
+
"version": "0.9.2-dev-914bee2.0+914bee2",
|
|
4
4
|
"description": "Persistent storage for SwingSet",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"lint:eslint": "eslint ."
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@agoric/internal": "0.3.3-dev-
|
|
24
|
+
"@agoric/internal": "0.3.3-dev-914bee2.0+914bee2",
|
|
25
25
|
"@endo/base64": "^1.0.7",
|
|
26
26
|
"@endo/bundle-source": "^3.4.0",
|
|
27
27
|
"@endo/check-bundle": "^1.0.9",
|
|
@@ -51,5 +51,5 @@
|
|
|
51
51
|
"typeCoverage": {
|
|
52
52
|
"atLeast": 78.77
|
|
53
53
|
},
|
|
54
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "914bee2aa7472039ba230267d72027b940bf8a1c"
|
|
55
55
|
}
|
package/src/transcriptStore.js
CHANGED
|
@@ -219,10 +219,12 @@ export function makeTranscriptStore(
|
|
|
219
219
|
*/
|
|
220
220
|
function initTranscript(vatID) {
|
|
221
221
|
ensureTxn();
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
|
|
222
|
+
const pos = 0;
|
|
223
|
+
const isCurrent = 1;
|
|
224
|
+
const incarnation = 0;
|
|
225
|
+
sqlWriteSpan.run(vatID, pos, pos, initialHash, isCurrent, incarnation);
|
|
226
|
+
const rec = spanRec(vatID, pos, pos, initialHash, isCurrent, incarnation);
|
|
227
|
+
noteExport(spanMetadataKey(rec), JSON.stringify(rec));
|
|
226
228
|
}
|
|
227
229
|
|
|
228
230
|
const sqlGetCurrentSpanBounds = db.prepare(`
|
|
@@ -255,32 +257,27 @@ export function makeTranscriptStore(
|
|
|
255
257
|
WHERE vatID = ? AND position >= ? AND position < ?
|
|
256
258
|
`);
|
|
257
259
|
|
|
258
|
-
|
|
260
|
+
/**
|
|
261
|
+
* Finalize a span, setting isCurrent to null, marking the resulting record
|
|
262
|
+
* for export, and effecting disk archival and database cleanup as
|
|
263
|
+
* configured.
|
|
264
|
+
* Note that creation of a new DB row and removal/replacement of the
|
|
265
|
+
* transcript.${vatID}.current export record are responsibility of the caller.
|
|
266
|
+
*
|
|
267
|
+
* @param {string} vatID
|
|
268
|
+
* @param {ReturnType<getCurrentSpanBounds>} bounds
|
|
269
|
+
*/
|
|
270
|
+
function closeSpan(vatID, bounds) {
|
|
259
271
|
ensureTxn();
|
|
260
|
-
const {
|
|
272
|
+
const { startPos, endPos, hash, incarnation } = bounds;
|
|
261
273
|
const rec = spanRec(vatID, startPos, endPos, hash, false, incarnation);
|
|
262
274
|
|
|
263
|
-
// add a new record for the now-old span
|
|
275
|
+
// add a new export record for the now-old span
|
|
264
276
|
noteExport(spanMetadataKey(rec), JSON.stringify(rec));
|
|
265
277
|
|
|
266
|
-
// and change its DB row to isCurrent=
|
|
278
|
+
// and change its DB row to isCurrent=null
|
|
267
279
|
sqlEndCurrentSpan.run(vatID);
|
|
268
280
|
|
|
269
|
-
// create a new (empty) row, with isCurrent=1
|
|
270
|
-
const incarnationToUse = isNewIncarnation ? incarnation + 1 : incarnation;
|
|
271
|
-
sqlWriteSpan.run(vatID, endPos, endPos, initialHash, 1, incarnationToUse);
|
|
272
|
-
|
|
273
|
-
// overwrite the transcript.${vatID}.current record with new span
|
|
274
|
-
const newRec = spanRec(
|
|
275
|
-
vatID,
|
|
276
|
-
endPos,
|
|
277
|
-
endPos,
|
|
278
|
-
initialHash,
|
|
279
|
-
true,
|
|
280
|
-
incarnationToUse,
|
|
281
|
-
);
|
|
282
|
-
noteExport(spanMetadataKey(newRec), JSON.stringify(newRec));
|
|
283
|
-
|
|
284
281
|
if (!keepTranscripts) {
|
|
285
282
|
// Delete items of the previously-current span.
|
|
286
283
|
// There may still be items associated with even older spans, but we leave
|
|
@@ -290,7 +287,32 @@ export function makeTranscriptStore(
|
|
|
290
287
|
// that doesn't include them.
|
|
291
288
|
sqlDeleteOldItems.run(vatID, startPos, endPos);
|
|
292
289
|
}
|
|
293
|
-
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function doSpanRollover(vatID, isNewIncarnation) {
|
|
293
|
+
ensureTxn();
|
|
294
|
+
const bounds = getCurrentSpanBounds(vatID);
|
|
295
|
+
const { endPos, incarnation } = bounds;
|
|
296
|
+
|
|
297
|
+
// deal with the now-old span
|
|
298
|
+
closeSpan(vatID, bounds);
|
|
299
|
+
|
|
300
|
+
// create a new (empty) DB row, with isCurrent=1
|
|
301
|
+
const newSpanIncarnation = isNewIncarnation ? incarnation + 1 : incarnation;
|
|
302
|
+
sqlWriteSpan.run(vatID, endPos, endPos, initialHash, 1, newSpanIncarnation);
|
|
303
|
+
|
|
304
|
+
// overwrite the transcript.${vatID}.current record with new span
|
|
305
|
+
const rec = spanRec(
|
|
306
|
+
vatID,
|
|
307
|
+
endPos,
|
|
308
|
+
endPos,
|
|
309
|
+
initialHash,
|
|
310
|
+
true,
|
|
311
|
+
newSpanIncarnation,
|
|
312
|
+
);
|
|
313
|
+
noteExport(spanMetadataKey(rec), JSON.stringify(rec));
|
|
314
|
+
|
|
315
|
+
return newSpanIncarnation;
|
|
294
316
|
}
|
|
295
317
|
|
|
296
318
|
/**
|
|
@@ -360,28 +382,21 @@ export function makeTranscriptStore(
|
|
|
360
382
|
`);
|
|
361
383
|
|
|
362
384
|
/**
|
|
363
|
-
* Prepare for vat deletion by marking the isCurrent span as not
|
|
364
|
-
*
|
|
385
|
+
* Prepare for vat deletion by marking the isCurrent=1 span as not current.
|
|
386
|
+
* Idempotent.
|
|
365
387
|
*
|
|
366
388
|
* @param {string} vatID The vat being terminated/deleted.
|
|
367
389
|
*/
|
|
368
390
|
function stopUsingTranscript(vatID) {
|
|
369
391
|
ensureTxn();
|
|
370
|
-
// this transforms the current span into a (short) historical one
|
|
371
|
-
// (basically doSpanRollover without adding replacement data)
|
|
372
392
|
const bounds = sqlGetCurrentSpanBounds.get(vatID);
|
|
373
|
-
if (bounds)
|
|
374
|
-
// add a new record for the now-old span
|
|
375
|
-
const { startPos, endPos, hash, incarnation } = bounds;
|
|
376
|
-
const rec = spanRec(vatID, startPos, endPos, hash, false, incarnation);
|
|
377
|
-
noteExport(spanMetadataKey(rec), JSON.stringify(rec));
|
|
393
|
+
if (!bounds) return;
|
|
378
394
|
|
|
379
|
-
|
|
380
|
-
|
|
395
|
+
// deal with the now-old span
|
|
396
|
+
closeSpan(vatID, bounds);
|
|
381
397
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
398
|
+
// remove the transcript.${vatID}.current record
|
|
399
|
+
noteExport(spanMetadataKey({ vatID, isCurrent: true }), undefined);
|
|
385
400
|
}
|
|
386
401
|
|
|
387
402
|
/**
|
|
@@ -542,7 +557,7 @@ export function makeTranscriptStore(
|
|
|
542
557
|
}
|
|
543
558
|
}
|
|
544
559
|
} else if (artifactMode === 'archival') {
|
|
545
|
-
// every span for all vatIDs that have an isCurrent span (to
|
|
560
|
+
// every span for all vatIDs that have an isCurrent=1 span (to
|
|
546
561
|
// ignore terminated/partially-deleted vats)
|
|
547
562
|
const vatIDs = new Set();
|
|
548
563
|
for (const { vatID } of sqlGetCurrentSpanMetadata.iterate()) {
|
package/test/deletion.test.js
CHANGED
|
@@ -4,6 +4,7 @@ import path from 'path';
|
|
|
4
4
|
|
|
5
5
|
import { Buffer } from 'node:buffer';
|
|
6
6
|
import sqlite3 from 'better-sqlite3';
|
|
7
|
+
import { arrayIsLike } from '@agoric/internal/tools/ava-assertions.js';
|
|
7
8
|
import { tmpDir } from './util.js';
|
|
8
9
|
import { initSwingStore } from '../src/swingStore.js';
|
|
9
10
|
import { makeSwingStoreExporter } from '../src/exporter.js';
|
|
@@ -50,32 +51,31 @@ test('delete snapshots with export callback', async t => {
|
|
|
50
51
|
|
|
51
52
|
await commit();
|
|
52
53
|
|
|
53
|
-
t.is(exportLog.length, 4);
|
|
54
|
-
t.is(exportLog[0][0], 'snapshot.v1.10');
|
|
55
|
-
t.is(exportLog[1][0], 'snapshot.v1.11');
|
|
56
|
-
t.is(exportLog[2][0], 'snapshot.v1.12');
|
|
57
|
-
t.is(exportLog[3][0], 'snapshot.v1.current');
|
|
58
54
|
const hash = JSON.parse(exportLog[0][1]).hash;
|
|
55
|
+
arrayIsLike(t, exportLog.splice(0), [
|
|
56
|
+
['snapshot.v1.10'],
|
|
57
|
+
['snapshot.v1.11'],
|
|
58
|
+
['snapshot.v1.12'],
|
|
59
|
+
['snapshot.v1.current'],
|
|
60
|
+
]);
|
|
59
61
|
t.deepEqual(exportData, {
|
|
60
62
|
'snapshot.v1.10': JSON.stringify({ vatID, snapPos: 10, hash, inUse: 0 }),
|
|
61
63
|
'snapshot.v1.11': JSON.stringify({ vatID, snapPos: 11, hash, inUse: 0 }),
|
|
62
64
|
'snapshot.v1.12': JSON.stringify({ vatID, snapPos: 12, hash, inUse: 1 }),
|
|
63
65
|
'snapshot.v1.current': 'snapshot.v1.12',
|
|
64
66
|
});
|
|
65
|
-
exportLog.length = 0;
|
|
66
67
|
|
|
67
68
|
// in a previous version, deleteVatSnapshots caused overlapping SQL
|
|
68
69
|
// queries, and failed
|
|
69
70
|
snapStore.deleteVatSnapshots(vatID);
|
|
70
71
|
await commit();
|
|
71
72
|
|
|
72
|
-
t.deepEqual(exportLog, [
|
|
73
|
+
t.deepEqual(exportLog.splice(0), [
|
|
73
74
|
['snapshot.v1.10', null],
|
|
74
75
|
['snapshot.v1.11', null],
|
|
75
76
|
['snapshot.v1.12', null],
|
|
76
77
|
['snapshot.v1.current', null],
|
|
77
78
|
]);
|
|
78
|
-
exportLog.length = 0;
|
|
79
79
|
t.deepEqual(exportData, {});
|
|
80
80
|
});
|
|
81
81
|
|
|
@@ -104,22 +104,20 @@ test('delete transcripts with export callback', async t => {
|
|
|
104
104
|
|
|
105
105
|
await commit();
|
|
106
106
|
|
|
107
|
-
t.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
arrayIsLike(t, exportLog.splice(0), [
|
|
108
|
+
['transcript.v1.0'],
|
|
109
|
+
['transcript.v1.current'],
|
|
110
|
+
]);
|
|
111
111
|
|
|
112
112
|
// in a previous version, deleteVatTranscripts caused overlapping SQL
|
|
113
113
|
// queries, and failed
|
|
114
114
|
transcriptStore.deleteVatTranscripts('v1');
|
|
115
115
|
await commit();
|
|
116
116
|
|
|
117
|
-
t.deepEqual(exportLog, [
|
|
117
|
+
t.deepEqual(exportLog.splice(0), [
|
|
118
118
|
['transcript.v1.0', null],
|
|
119
119
|
['transcript.v1.current', null],
|
|
120
120
|
]);
|
|
121
|
-
|
|
122
|
-
exportLog.length = 0;
|
|
123
121
|
});
|
|
124
122
|
|
|
125
123
|
const getExport = async (dbDir, artifactMode) => {
|
|
@@ -145,16 +143,20 @@ const reImport = async (t, dbDir, artifactMode) => {
|
|
|
145
143
|
return sqlite3(path.join(dbDir2, 'swingstore.sqlite'));
|
|
146
144
|
};
|
|
147
145
|
|
|
148
|
-
const
|
|
149
|
-
const
|
|
150
|
-
|
|
146
|
+
const stripHashes = exportData => {
|
|
147
|
+
const stripped = {};
|
|
148
|
+
const entries =
|
|
149
|
+
exportData instanceof Map
|
|
150
|
+
? exportData.entries()
|
|
151
|
+
: Object.entries(exportData);
|
|
152
|
+
for (const [key, value] of entries) {
|
|
151
153
|
const { hash: _, ...data } = JSON.parse(value);
|
|
152
|
-
|
|
154
|
+
stripped[key] = data;
|
|
153
155
|
}
|
|
154
|
-
return
|
|
156
|
+
return stripped;
|
|
155
157
|
};
|
|
156
158
|
|
|
157
|
-
const setupTranscript = async t => {
|
|
159
|
+
const setupTranscript = async (t, keepTranscripts) => {
|
|
158
160
|
const vatID = 'v1';
|
|
159
161
|
const exportLog = [];
|
|
160
162
|
const currentExportData = {};
|
|
@@ -166,7 +168,7 @@ const setupTranscript = async t => {
|
|
|
166
168
|
};
|
|
167
169
|
const [dbDir, cleanup] = await tmpDir('testdb');
|
|
168
170
|
t.teardown(cleanup);
|
|
169
|
-
const store = initSwingStore(dbDir, { exportCallback });
|
|
171
|
+
const store = initSwingStore(dbDir, { exportCallback, keepTranscripts });
|
|
170
172
|
const { kernelStorage, hostStorage } = store;
|
|
171
173
|
const { transcriptStore } = kernelStorage;
|
|
172
174
|
const { commit } = hostStorage;
|
|
@@ -199,7 +201,11 @@ const setupTranscript = async t => {
|
|
|
199
201
|
};
|
|
200
202
|
};
|
|
201
203
|
|
|
202
|
-
|
|
204
|
+
/**
|
|
205
|
+
* @param {import('ava').ExecutionContext} t
|
|
206
|
+
* @param {{ keepTranscripts: boolean }} config
|
|
207
|
+
*/
|
|
208
|
+
const execSlowTranscriptDeletion = async (t, { keepTranscripts }) => {
|
|
203
209
|
// slow transcript deletion should remove export-data as it removes
|
|
204
210
|
// transcript spans and their items
|
|
205
211
|
|
|
@@ -211,27 +217,42 @@ test('slow deletion of transcripts', async t => {
|
|
|
211
217
|
exportLog,
|
|
212
218
|
currentExportData,
|
|
213
219
|
vatID,
|
|
214
|
-
} = await setupTranscript(t);
|
|
215
|
-
|
|
216
|
-
t.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
} = await setupTranscript(t, keepTranscripts);
|
|
221
|
+
|
|
222
|
+
arrayIsLike(t, exportLog.splice(0), [
|
|
223
|
+
['transcript.v1.0'],
|
|
224
|
+
['transcript.v1.2'],
|
|
225
|
+
['transcript.v1.4'],
|
|
226
|
+
['transcript.v1.current'],
|
|
227
|
+
]);
|
|
222
228
|
const t0 = { vatID, startPos: 0, endPos: 2, isCurrent: 0, incarnation: 0 };
|
|
223
229
|
const t2 = { vatID, startPos: 2, endPos: 4, isCurrent: 0, incarnation: 0 };
|
|
224
230
|
const t4 = { vatID, startPos: 4, endPos: 6, isCurrent: 0, incarnation: 1 };
|
|
225
|
-
const tc = { vatID, startPos: 6, endPos: 8, isCurrent: 1, incarnation: 1 };
|
|
226
231
|
const t6 = { vatID, startPos: 6, endPos: 8, isCurrent: 0, incarnation: 1 };
|
|
227
|
-
|
|
232
|
+
const expectedLiveExportData = {
|
|
228
233
|
'transcript.v1.0': t0,
|
|
229
234
|
'transcript.v1.2': t2,
|
|
230
235
|
'transcript.v1.4': t4,
|
|
231
|
-
'transcript.v1.current':
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
|
|
236
|
+
'transcript.v1.current': { ...t6, isCurrent: 1 },
|
|
237
|
+
};
|
|
238
|
+
const expectedStoppedExportData = {
|
|
239
|
+
'transcript.v1.0': t0,
|
|
240
|
+
'transcript.v1.2': t2,
|
|
241
|
+
'transcript.v1.4': t4,
|
|
242
|
+
'transcript.v1.6': t6,
|
|
243
|
+
};
|
|
244
|
+
const expectedArtifactNames = [
|
|
245
|
+
'transcript.v1.0.2',
|
|
246
|
+
'transcript.v1.2.4',
|
|
247
|
+
'transcript.v1.4.6',
|
|
248
|
+
'transcript.v1.6.8',
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
t.deepEqual(stripHashes(currentExportData), expectedLiveExportData);
|
|
252
|
+
t.is(
|
|
253
|
+
db.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(),
|
|
254
|
+
keepTranscripts ? 8 : 2,
|
|
255
|
+
);
|
|
235
256
|
t.is(db.prepare('SELECT COUNT(*) FROM transcriptSpans').pluck().get(), 4);
|
|
236
257
|
|
|
237
258
|
// an "operational"-mode export should list all spans, but only have
|
|
@@ -239,34 +260,21 @@ test('slow deletion of transcripts', async t => {
|
|
|
239
260
|
{
|
|
240
261
|
const { exportData, artifactNames } = await getExport(dbDir, 'operational');
|
|
241
262
|
t.deepEqual(currentExportData, mapToObj(exportData));
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
'transcript.v1.2': t2,
|
|
245
|
-
'transcript.v1.4': t4,
|
|
246
|
-
'transcript.v1.current': tc,
|
|
247
|
-
});
|
|
248
|
-
t.deepEqual(artifactNames, ['transcript.v1.6.8']);
|
|
263
|
+
t.deepEqual(stripHashes(exportData), expectedLiveExportData);
|
|
264
|
+
t.deepEqual(artifactNames, expectedArtifactNames.slice(-1));
|
|
249
265
|
const db2 = await reImport(t, dbDir, 'operational');
|
|
250
266
|
t.is(db2.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(), 2);
|
|
251
267
|
t.is(db2.prepare('SELECT COUNT(*) FROM transcriptSpans').pluck().get(), 4);
|
|
252
268
|
}
|
|
253
269
|
|
|
254
|
-
// an "archival"-mode export should list all four spans
|
|
255
|
-
// artifacts for each
|
|
256
|
-
{
|
|
270
|
+
// an "archival"-mode export should list all four spans with
|
|
271
|
+
// artifacts for each, but is only available with keepTranscripts=true
|
|
272
|
+
if (!keepTranscripts) {
|
|
273
|
+
await t.throwsAsync(getExport(dbDir, 'archival'));
|
|
274
|
+
} else {
|
|
257
275
|
const { exportData, artifactNames } = await getExport(dbDir, 'archival');
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
'transcript.v1.2': t2,
|
|
261
|
-
'transcript.v1.4': t4,
|
|
262
|
-
'transcript.v1.current': tc,
|
|
263
|
-
});
|
|
264
|
-
t.deepEqual(artifactNames, [
|
|
265
|
-
'transcript.v1.0.2',
|
|
266
|
-
'transcript.v1.2.4',
|
|
267
|
-
'transcript.v1.4.6',
|
|
268
|
-
'transcript.v1.6.8',
|
|
269
|
-
]);
|
|
276
|
+
t.deepEqual(stripHashes(exportData), expectedLiveExportData);
|
|
277
|
+
t.deepEqual(artifactNames, expectedArtifactNames);
|
|
270
278
|
const db2 = await reImport(t, dbDir, 'archival');
|
|
271
279
|
t.is(db2.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(), 8);
|
|
272
280
|
t.is(db2.prepare('SELECT COUNT(*) FROM transcriptSpans').pluck().get(), 4);
|
|
@@ -278,58 +286,49 @@ test('slow deletion of transcripts', async t => {
|
|
|
278
286
|
{
|
|
279
287
|
transcriptStore.stopUsingTranscript(vatID);
|
|
280
288
|
await commit();
|
|
281
|
-
|
|
282
|
-
'transcript.v1.0': t0,
|
|
283
|
-
'transcript.v1.2': t2,
|
|
284
|
-
'transcript.v1.4': t4,
|
|
285
|
-
'transcript.v1.6': t6,
|
|
286
|
-
});
|
|
289
|
+
t.deepEqual(stripHashes(currentExportData), expectedStoppedExportData);
|
|
287
290
|
exportLog.length = 0;
|
|
288
291
|
// stopUsingTranscript is idempotent
|
|
289
292
|
transcriptStore.stopUsingTranscript(vatID);
|
|
290
293
|
await commit();
|
|
291
|
-
t.
|
|
294
|
+
t.deepEqual(exportLog, []);
|
|
292
295
|
}
|
|
293
296
|
|
|
294
297
|
// All exports (debug and non-debug) in this "terminated but not
|
|
295
298
|
// deleted" state will still have the export-data keys. Only
|
|
296
|
-
// debug-mode will have artifacts.
|
|
299
|
+
// debug-mode will have artifacts (and only with keepTranscripts=true).
|
|
297
300
|
for (const mode of ['operational', 'replay', 'archival', 'debug']) {
|
|
298
301
|
const { exportData, artifactNames } = await getExport(dbDir, mode);
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
'transcript.v1.4.6',
|
|
310
|
-
'transcript.v1.6.8',
|
|
311
|
-
]);
|
|
312
|
-
} else {
|
|
313
|
-
t.deepEqual(artifactNames, []);
|
|
314
|
-
}
|
|
302
|
+
t.deepEqual(
|
|
303
|
+
stripHashes(exportData),
|
|
304
|
+
expectedStoppedExportData,
|
|
305
|
+
`${mode} stopped-vat export data`,
|
|
306
|
+
);
|
|
307
|
+
t.deepEqual(
|
|
308
|
+
artifactNames,
|
|
309
|
+
mode === 'debug' && keepTranscripts ? expectedArtifactNames : [],
|
|
310
|
+
`${mode} stopped-vat export artifacts`,
|
|
311
|
+
);
|
|
315
312
|
const db2 = await reImport(t, dbDir, 'operational');
|
|
316
313
|
t.is(db2.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(), 0);
|
|
317
314
|
t.is(db2.prepare('SELECT COUNT(*) FROM transcriptSpans').pluck().get(), 4);
|
|
318
315
|
}
|
|
319
316
|
|
|
320
317
|
// first deletion
|
|
318
|
+
const expectedTruncatedExportData1 = Object.fromEntries(
|
|
319
|
+
Object.entries(expectedStoppedExportData).slice(0, -1),
|
|
320
|
+
);
|
|
321
321
|
{
|
|
322
322
|
// budget=1 will let it delete one span, the last one
|
|
323
323
|
const dc = transcriptStore.deleteVatTranscripts(vatID, 1);
|
|
324
324
|
t.false(dc.done);
|
|
325
325
|
t.is(dc.cleanups, 1);
|
|
326
326
|
await commit();
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
'
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
t.is(db.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(), 6);
|
|
327
|
+
t.deepEqual(stripHashes(currentExportData), expectedTruncatedExportData1);
|
|
328
|
+
t.is(
|
|
329
|
+
db.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(),
|
|
330
|
+
keepTranscripts ? 6 : 0,
|
|
331
|
+
);
|
|
333
332
|
t.is(db.prepare('SELECT COUNT(*) FROM transcriptSpans').pluck().get(), 3);
|
|
334
333
|
}
|
|
335
334
|
|
|
@@ -341,50 +340,53 @@ test('slow deletion of transcripts', async t => {
|
|
|
341
340
|
|
|
342
341
|
for (const mode of ['operational', 'replay', 'archival', 'debug']) {
|
|
343
342
|
const { exportData, artifactNames } = await getExport(dbDir, mode);
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
t.deepEqual(artifactNames, []);
|
|
357
|
-
}
|
|
343
|
+
t.deepEqual(
|
|
344
|
+
stripHashes(exportData),
|
|
345
|
+
expectedTruncatedExportData1,
|
|
346
|
+
`${mode} first-deletion export data`,
|
|
347
|
+
);
|
|
348
|
+
t.deepEqual(
|
|
349
|
+
artifactNames,
|
|
350
|
+
mode === 'debug' && keepTranscripts
|
|
351
|
+
? expectedArtifactNames.slice(0, -1)
|
|
352
|
+
: [],
|
|
353
|
+
`${mode} first-deletion export artifacts`,
|
|
354
|
+
);
|
|
358
355
|
const db2 = await reImport(t, dbDir, 'operational');
|
|
359
356
|
t.is(db2.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(), 0);
|
|
360
357
|
t.is(db2.prepare('SELECT COUNT(*) FROM transcriptSpans').pluck().get(), 3);
|
|
361
358
|
}
|
|
362
359
|
|
|
363
360
|
// second deletion
|
|
361
|
+
const expectedTruncatedExportData2 = Object.fromEntries(
|
|
362
|
+
Object.entries(expectedStoppedExportData).slice(0, -2),
|
|
363
|
+
);
|
|
364
364
|
{
|
|
365
365
|
const dc = transcriptStore.deleteVatTranscripts(vatID, 1);
|
|
366
366
|
t.false(dc.done);
|
|
367
367
|
t.is(dc.cleanups, 1);
|
|
368
368
|
await commit();
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
'
|
|
372
|
-
|
|
373
|
-
|
|
369
|
+
t.deepEqual(stripHashes(currentExportData), expectedTruncatedExportData2);
|
|
370
|
+
t.is(
|
|
371
|
+
db.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(),
|
|
372
|
+
keepTranscripts ? 4 : 0,
|
|
373
|
+
);
|
|
374
374
|
t.is(db.prepare('SELECT COUNT(*) FROM transcriptSpans').pluck().get(), 2);
|
|
375
375
|
}
|
|
376
|
-
|
|
377
376
|
for (const mode of ['operational', 'replay', 'archival', 'debug']) {
|
|
378
377
|
const { exportData, artifactNames } = await getExport(dbDir, mode);
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
378
|
+
t.deepEqual(
|
|
379
|
+
stripHashes(exportData),
|
|
380
|
+
expectedTruncatedExportData2,
|
|
381
|
+
`${mode} second-deletion export data`,
|
|
382
|
+
);
|
|
383
|
+
t.deepEqual(
|
|
384
|
+
artifactNames,
|
|
385
|
+
mode === 'debug' && keepTranscripts
|
|
386
|
+
? expectedArtifactNames.slice(0, -2)
|
|
387
|
+
: [],
|
|
388
|
+
`${mode} second-deletion export artifacts`,
|
|
389
|
+
);
|
|
388
390
|
const db2 = await reImport(t, dbDir, 'operational');
|
|
389
391
|
t.is(db2.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(), 0);
|
|
390
392
|
t.is(db2.prepare('SELECT COUNT(*) FROM transcriptSpans').pluck().get(), 2);
|
|
@@ -396,15 +398,18 @@ test('slow deletion of transcripts', async t => {
|
|
|
396
398
|
t.true(dc.done);
|
|
397
399
|
t.is(dc.cleanups, 2);
|
|
398
400
|
await commit();
|
|
399
|
-
|
|
401
|
+
t.deepEqual(stripHashes(currentExportData), {});
|
|
400
402
|
t.is(db.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(), 0);
|
|
401
403
|
t.is(db.prepare('SELECT COUNT(*) FROM transcriptSpans').pluck().get(), 0);
|
|
402
404
|
}
|
|
403
|
-
|
|
404
405
|
for (const mode of ['operational', 'replay', 'archival', 'debug']) {
|
|
405
406
|
const { exportData, artifactNames } = await getExport(dbDir, mode);
|
|
406
|
-
|
|
407
|
-
|
|
407
|
+
t.deepEqual(
|
|
408
|
+
stripHashes(exportData),
|
|
409
|
+
{},
|
|
410
|
+
`${mode} final-deletion export data`,
|
|
411
|
+
);
|
|
412
|
+
t.deepEqual(artifactNames, [], `${mode} final-deletion export artifacts`);
|
|
408
413
|
const db2 = await reImport(t, dbDir, 'operational');
|
|
409
414
|
t.is(db2.prepare('SELECT COUNT(*) FROM transcriptItems').pluck().get(), 0);
|
|
410
415
|
t.is(db2.prepare('SELECT COUNT(*) FROM transcriptSpans').pluck().get(), 0);
|
|
@@ -417,9 +422,18 @@ test('slow deletion of transcripts', async t => {
|
|
|
417
422
|
t.true(dc.done);
|
|
418
423
|
t.is(dc.cleanups, 0);
|
|
419
424
|
await commit();
|
|
420
|
-
t.
|
|
425
|
+
t.deepEqual(exportLog, []);
|
|
421
426
|
}
|
|
427
|
+
};
|
|
428
|
+
const testSlowTranscriptDeletion = test.macro({
|
|
429
|
+
title(extra = '', { keepTranscripts }) {
|
|
430
|
+
const detail = keepTranscripts ? 'with retention' : 'without retention';
|
|
431
|
+
return `slow deletion of transcripts ${detail}${extra.replace(/.$/, ' $&')}`;
|
|
432
|
+
},
|
|
433
|
+
exec: execSlowTranscriptDeletion,
|
|
422
434
|
});
|
|
435
|
+
test(testSlowTranscriptDeletion, { keepTranscripts: true });
|
|
436
|
+
test(testSlowTranscriptDeletion, { keepTranscripts: false });
|
|
423
437
|
|
|
424
438
|
test('slow deletion without stopUsingTranscript', async t => {
|
|
425
439
|
// slow deletion should work even without stopUsingTranscript
|
|
@@ -438,7 +452,7 @@ test('slow deletion without stopUsingTranscript', async t => {
|
|
|
438
452
|
const t0 = { vatID, startPos: 0, endPos: 2, isCurrent: 0, incarnation: 0 };
|
|
439
453
|
const t2 = { vatID, startPos: 2, endPos: 4, isCurrent: 0, incarnation: 0 };
|
|
440
454
|
const t4 = { vatID, startPos: 4, endPos: 6, isCurrent: 0, incarnation: 1 };
|
|
441
|
-
|
|
455
|
+
t.deepEqual(stripHashes(currentExportData), {
|
|
442
456
|
'transcript.v1.0': t0,
|
|
443
457
|
'transcript.v1.2': t2,
|
|
444
458
|
'transcript.v1.4': t4,
|
|
@@ -556,7 +570,7 @@ test('slow deletion of snapshots', async t => {
|
|
|
556
570
|
// stopUsingLastSnapshot is idempotent
|
|
557
571
|
snapStore.stopUsingLastSnapshot(vatID);
|
|
558
572
|
await commit();
|
|
559
|
-
t.
|
|
573
|
+
t.deepEqual(exportLog, []);
|
|
560
574
|
}
|
|
561
575
|
|
|
562
576
|
// first deletion
|