@agoric/swing-store 0.9.2-upgrade-16-dev-0df76a7.0 → 0.9.2-upgrade-17-dev-e67cd91.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/CHANGELOG.md +0 -34
- package/docs/bundlestore.md +30 -0
- package/docs/data-export.md +6 -6
- package/docs/kvstore.md +35 -0
- package/docs/snapstore.md +49 -0
- package/docs/swingstore.md +62 -6
- package/docs/transcriptstore.md +70 -0
- package/package.json +10 -10
- package/src/archiver.js +80 -0
- package/src/bundleStore.js +1 -1
- package/src/exporter.js +5 -5
- package/src/hasher.js +1 -1
- package/src/importer.js +1 -1
- package/src/index.js +2 -0
- package/src/internal.js +1 -1
- package/src/kvStore.js +1 -1
- package/src/repairMetadata.js +1 -1
- package/src/snapStore.js +113 -77
- package/src/swingStore.js +11 -2
- package/src/transcriptStore.js +281 -141
- package/test/deletion.test.js +644 -20
- package/test/export.test.js +4 -4
- package/test/exportImport.test.js +1 -1
- package/test/snapstore.test.js +47 -2
- package/test/state.test.js +91 -67
package/src/snapStore.js
CHANGED
|
@@ -3,11 +3,8 @@ import { createHash } from 'crypto';
|
|
|
3
3
|
import { finished as finishedCallback, PassThrough, Readable } from 'stream';
|
|
4
4
|
import { promisify } from 'util';
|
|
5
5
|
import { createGzip, createGunzip } from 'zlib';
|
|
6
|
-
import { Fail, q } from '@
|
|
7
|
-
import {
|
|
8
|
-
aggregateTryFinally,
|
|
9
|
-
PromiseAllOrErrors,
|
|
10
|
-
} from '@agoric/internal/src/node/utils.js';
|
|
6
|
+
import { Fail, q } from '@endo/errors';
|
|
7
|
+
import { withDeferredCleanup } from '@agoric/internal';
|
|
11
8
|
import { buffer } from './util.js';
|
|
12
9
|
|
|
13
10
|
/**
|
|
@@ -17,6 +14,7 @@ import { buffer } from './util.js';
|
|
|
17
14
|
* @property {number} dbSaveSeconds time to write snapshot in DB
|
|
18
15
|
* @property {number} compressedSize size of (compressed) snapshot
|
|
19
16
|
* @property {number} compressSeconds time to generate and compress the snapshot
|
|
17
|
+
* @property {number} [archiveWriteSeconds] time to write an archive to disk (if applicable)
|
|
20
18
|
*/
|
|
21
19
|
|
|
22
20
|
/**
|
|
@@ -39,7 +37,7 @@ import { buffer } from './util.js';
|
|
|
39
37
|
* loadSnapshot: (vatID: string) => AsyncIterableIterator<Uint8Array>,
|
|
40
38
|
* saveSnapshot: (vatID: string, snapPos: number, snapshotStream: AsyncIterable<Uint8Array>) => Promise<SnapshotResult>,
|
|
41
39
|
* deleteAllUnusedSnapshots: () => void,
|
|
42
|
-
* deleteVatSnapshots: (vatID: string) =>
|
|
40
|
+
* deleteVatSnapshots: (vatID: string, budget?: number) => { done: boolean, cleanups: number },
|
|
43
41
|
* stopUsingLastSnapshot: (vatID: string) => void,
|
|
44
42
|
* getSnapshotInfo: (vatID: string) => SnapshotInfo,
|
|
45
43
|
* }} SnapStore
|
|
@@ -73,6 +71,7 @@ const finished = promisify(finishedCallback);
|
|
|
73
71
|
* @param {(key: string, value: string | undefined) => void} noteExport
|
|
74
72
|
* @param {object} [options]
|
|
75
73
|
* @param {boolean | undefined} [options.keepSnapshots]
|
|
74
|
+
* @param {(name: string, compressedData: Parameters<import('stream').Readable.from>[0]) => Promise<void>} [options.archiveSnapshot]
|
|
76
75
|
* @returns {SnapStore & SnapStoreInternal & SnapStoreDebug}
|
|
77
76
|
*/
|
|
78
77
|
export function makeSnapStore(
|
|
@@ -80,7 +79,7 @@ export function makeSnapStore(
|
|
|
80
79
|
ensureTxn,
|
|
81
80
|
{ measureSeconds },
|
|
82
81
|
noteExport = () => {},
|
|
83
|
-
{ keepSnapshots = false } = {},
|
|
82
|
+
{ keepSnapshots = false, archiveSnapshot } = {},
|
|
84
83
|
) {
|
|
85
84
|
db.exec(`
|
|
86
85
|
CREATE TABLE IF NOT EXISTS snapshots (
|
|
@@ -173,11 +172,13 @@ export function makeSnapStore(
|
|
|
173
172
|
`);
|
|
174
173
|
|
|
175
174
|
function stopUsingLastSnapshot(vatID) {
|
|
175
|
+
// idempotent
|
|
176
176
|
ensureTxn();
|
|
177
177
|
const oldInfo = sqlGetPriorSnapshotInfo.get(vatID);
|
|
178
178
|
if (oldInfo) {
|
|
179
179
|
const rec = snapshotRec(vatID, oldInfo.snapPos, oldInfo.hash, 0);
|
|
180
180
|
noteExport(snapshotMetadataKey(rec), JSON.stringify(rec));
|
|
181
|
+
noteExport(currentSnapshotMetadataKey(rec), undefined);
|
|
181
182
|
if (keepSnapshots) {
|
|
182
183
|
sqlStopUsingLastSnapshot.run(vatID);
|
|
183
184
|
} else {
|
|
@@ -194,7 +195,8 @@ export function makeSnapStore(
|
|
|
194
195
|
|
|
195
196
|
/**
|
|
196
197
|
* Generates a new XS heap snapshot, stores a gzipped copy of it into the
|
|
197
|
-
* snapshots table
|
|
198
|
+
* snapshots table (and also to an archiveSnapshot callback if provided for
|
|
199
|
+
* e.g. disk archival), and reports information about the process, including
|
|
198
200
|
* snapshot size and timing metrics.
|
|
199
201
|
*
|
|
200
202
|
* @param {string} vatID
|
|
@@ -203,74 +205,62 @@ export function makeSnapStore(
|
|
|
203
205
|
* @returns {Promise<SnapshotResult>}
|
|
204
206
|
*/
|
|
205
207
|
async function saveSnapshot(vatID, snapPos, snapshotStream) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
new Promise((resolve, reject) =>
|
|
220
|
-
snapReader.destroy(
|
|
221
|
-
null,
|
|
222
|
-
// @ts-expect-error incorrect types
|
|
223
|
-
err => (err ? reject(err) : resolve()),
|
|
224
|
-
),
|
|
225
|
-
),
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
snapReader.on('data', chunk => {
|
|
229
|
-
uncompressedSize += chunk.length;
|
|
230
|
-
});
|
|
231
|
-
snapReader.pipe(hashStream);
|
|
232
|
-
const compressedSnapshotData = await buffer(snapReader.pipe(gzip));
|
|
233
|
-
await finished(snapReader);
|
|
234
|
-
return compressedSnapshotData;
|
|
208
|
+
return withDeferredCleanup(async addCleanup => {
|
|
209
|
+
const hashStream = createHash('sha256');
|
|
210
|
+
const gzip = createGzip();
|
|
211
|
+
let compressedSize = 0;
|
|
212
|
+
let uncompressedSize = 0;
|
|
213
|
+
|
|
214
|
+
const { duration: compressSeconds, result: compressedSnapshot } =
|
|
215
|
+
await measureSeconds(async () => {
|
|
216
|
+
const snapReader = Readable.from(snapshotStream);
|
|
217
|
+
const destroyReader = promisify(snapReader.destroy.bind(snapReader));
|
|
218
|
+
addCleanup(() => destroyReader(null));
|
|
219
|
+
snapReader.on('data', chunk => {
|
|
220
|
+
uncompressedSize += chunk.length;
|
|
235
221
|
});
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
stopUsingLastSnapshot(vatID);
|
|
241
|
-
compressedSize = compressedSnapshot.length;
|
|
242
|
-
sqlSaveSnapshot.run(
|
|
243
|
-
vatID,
|
|
244
|
-
snapPos,
|
|
245
|
-
1,
|
|
246
|
-
hash,
|
|
247
|
-
uncompressedSize,
|
|
248
|
-
compressedSize,
|
|
249
|
-
compressedSnapshot,
|
|
250
|
-
);
|
|
251
|
-
const rec = snapshotRec(vatID, snapPos, hash, 1);
|
|
252
|
-
const exportKey = snapshotMetadataKey(rec);
|
|
253
|
-
noteExport(exportKey, JSON.stringify(rec));
|
|
254
|
-
noteExport(
|
|
255
|
-
currentSnapshotMetadataKey(rec),
|
|
256
|
-
snapshotArtifactName(rec),
|
|
257
|
-
);
|
|
222
|
+
snapReader.pipe(hashStream);
|
|
223
|
+
const compressedSnapshotData = await buffer(snapReader.pipe(gzip));
|
|
224
|
+
await finished(snapReader);
|
|
225
|
+
return compressedSnapshotData;
|
|
258
226
|
});
|
|
227
|
+
const hash = hashStream.digest('hex');
|
|
228
|
+
const rec = snapshotRec(vatID, snapPos, hash, 1);
|
|
229
|
+
const exportKey = snapshotMetadataKey(rec);
|
|
259
230
|
|
|
260
|
-
|
|
231
|
+
const { duration: dbSaveSeconds } = await measureSeconds(async () => {
|
|
232
|
+
ensureTxn();
|
|
233
|
+
stopUsingLastSnapshot(vatID);
|
|
234
|
+
compressedSize = compressedSnapshot.length;
|
|
235
|
+
sqlSaveSnapshot.run(
|
|
236
|
+
vatID,
|
|
237
|
+
snapPos,
|
|
238
|
+
1,
|
|
261
239
|
hash,
|
|
262
240
|
uncompressedSize,
|
|
263
|
-
compressSeconds,
|
|
264
|
-
dbSaveSeconds,
|
|
265
241
|
compressedSize,
|
|
266
|
-
|
|
267
|
-
},
|
|
268
|
-
async () => {
|
|
269
|
-
await PromiseAllOrErrors(
|
|
270
|
-
cleanup.reverse().map(fn => Promise.resolve().then(() => fn())),
|
|
242
|
+
compressedSnapshot,
|
|
271
243
|
);
|
|
272
|
-
|
|
273
|
-
|
|
244
|
+
noteExport(exportKey, JSON.stringify(rec));
|
|
245
|
+
noteExport(currentSnapshotMetadataKey(rec), snapshotArtifactName(rec));
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
let archiveWriteSeconds;
|
|
249
|
+
if (archiveSnapshot) {
|
|
250
|
+
({ duration: archiveWriteSeconds } = await measureSeconds(async () => {
|
|
251
|
+
await archiveSnapshot(exportKey, compressedSnapshot);
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return harden({
|
|
256
|
+
hash,
|
|
257
|
+
uncompressedSize,
|
|
258
|
+
compressSeconds,
|
|
259
|
+
dbSaveSeconds,
|
|
260
|
+
archiveWriteSeconds,
|
|
261
|
+
compressedSize,
|
|
262
|
+
});
|
|
263
|
+
});
|
|
274
264
|
}
|
|
275
265
|
|
|
276
266
|
const sqlGetSnapshot = db.prepare(`
|
|
@@ -354,28 +344,74 @@ export function makeSnapStore(
|
|
|
354
344
|
WHERE vatID = ?
|
|
355
345
|
`);
|
|
356
346
|
|
|
347
|
+
const sqlDeleteOneVatSnapshot = db.prepare(`
|
|
348
|
+
DELETE FROM snapshots
|
|
349
|
+
WHERE vatID = ? AND snapPos = ?
|
|
350
|
+
`);
|
|
351
|
+
|
|
357
352
|
const sqlGetSnapshotList = db.prepare(`
|
|
358
353
|
SELECT snapPos
|
|
359
354
|
FROM snapshots
|
|
360
355
|
WHERE vatID = ?
|
|
361
356
|
ORDER BY snapPos
|
|
362
357
|
`);
|
|
363
|
-
|
|
358
|
+
|
|
359
|
+
const sqlGetSnapshotListLimited = db.prepare(`
|
|
360
|
+
SELECT snapPos, inUse
|
|
361
|
+
FROM snapshots
|
|
362
|
+
WHERE vatID = ?
|
|
363
|
+
ORDER BY snapPos DESC
|
|
364
|
+
LIMIT ?
|
|
365
|
+
`);
|
|
364
366
|
|
|
365
367
|
/**
|
|
366
|
-
*
|
|
368
|
+
* @param {string} vatID
|
|
369
|
+
* @returns {boolean}
|
|
370
|
+
*/
|
|
371
|
+
function hasSnapshots(vatID) {
|
|
372
|
+
// the LIMIT 1 means we aren't really getting all entries
|
|
373
|
+
return sqlGetSnapshotListLimited.all(vatID, 1).length > 0;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Delete some or all snapshots for a given vat (for use when, e.g.,
|
|
378
|
+
* a vat is terminated)
|
|
367
379
|
*
|
|
368
380
|
* @param {string} vatID
|
|
381
|
+
* @param {number} [budget]
|
|
382
|
+
* @returns {{ done: boolean, cleanups: number }}
|
|
369
383
|
*/
|
|
370
|
-
function deleteVatSnapshots(vatID) {
|
|
384
|
+
function deleteVatSnapshots(vatID, budget = Infinity) {
|
|
371
385
|
ensureTxn();
|
|
372
|
-
const
|
|
373
|
-
|
|
386
|
+
const deleteAll = budget === Infinity;
|
|
387
|
+
assert(deleteAll || budget >= 1, 'budget must be undefined or positive');
|
|
388
|
+
// We can't use .iterate because noteExport can write to the DB,
|
|
389
|
+
// and overlapping queries are not supported.
|
|
390
|
+
const deletions = deleteAll
|
|
391
|
+
? sqlGetSnapshotList.all(vatID)
|
|
392
|
+
: sqlGetSnapshotListLimited.all(vatID, budget);
|
|
393
|
+
let clearCurrent = deleteAll;
|
|
394
|
+
for (const deletion of deletions) {
|
|
395
|
+
clearCurrent ||= deletion.inUse;
|
|
396
|
+
const { snapPos } = deletion;
|
|
374
397
|
const exportRec = snapshotRec(vatID, snapPos, undefined);
|
|
375
398
|
noteExport(snapshotMetadataKey(exportRec), undefined);
|
|
399
|
+
// Budgeted deletion must delete rows one by one,
|
|
400
|
+
// but full deletion is handled all at once after this loop.
|
|
401
|
+
if (!deleteAll) {
|
|
402
|
+
sqlDeleteOneVatSnapshot.run(vatID, snapPos);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (deleteAll) {
|
|
406
|
+
sqlDeleteVatSnapshots.run(vatID);
|
|
407
|
+
}
|
|
408
|
+
if (clearCurrent) {
|
|
409
|
+
noteExport(currentSnapshotMetadataKey({ vatID }), undefined);
|
|
376
410
|
}
|
|
377
|
-
|
|
378
|
-
|
|
411
|
+
return {
|
|
412
|
+
done: deleteAll || deletions.length === 0 || !hasSnapshots(vatID),
|
|
413
|
+
cleanups: deletions.length,
|
|
414
|
+
};
|
|
379
415
|
}
|
|
380
416
|
|
|
381
417
|
const sqlGetSnapshotInfo = db.prepare(`
|
|
@@ -452,7 +488,7 @@ export function makeSnapStore(
|
|
|
452
488
|
`);
|
|
453
489
|
|
|
454
490
|
/**
|
|
455
|
-
* Obtain artifact metadata records for
|
|
491
|
+
* Obtain artifact metadata records for snapshots contained in this store.
|
|
456
492
|
*
|
|
457
493
|
* @param {boolean} includeHistorical If true, include all metadata that is
|
|
458
494
|
* present in the store regardless of its currency; if false, only include
|
package/src/swingStore.js
CHANGED
|
@@ -5,7 +5,7 @@ import * as path from 'path';
|
|
|
5
5
|
|
|
6
6
|
import sqlite3 from 'better-sqlite3';
|
|
7
7
|
|
|
8
|
-
import { Fail, q } from '@
|
|
8
|
+
import { Fail, q } from '@endo/errors';
|
|
9
9
|
|
|
10
10
|
import { dbFileInDirectory } from './util.js';
|
|
11
11
|
import { makeKVStore, getKeyType } from './kvStore.js';
|
|
@@ -169,7 +169,13 @@ export function makeSwingStore(dirPath, forceReset, options = {}) {
|
|
|
169
169
|
filePath = ':memory:';
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
const {
|
|
172
|
+
const {
|
|
173
|
+
traceFile,
|
|
174
|
+
keepSnapshots,
|
|
175
|
+
keepTranscripts,
|
|
176
|
+
archiveSnapshot,
|
|
177
|
+
archiveTranscript,
|
|
178
|
+
} = options;
|
|
173
179
|
|
|
174
180
|
let traceOutput = traceFile
|
|
175
181
|
? fs.createWriteStream(path.resolve(traceFile), {
|
|
@@ -297,6 +303,7 @@ export function makeSwingStore(dirPath, forceReset, options = {}) {
|
|
|
297
303
|
noteExport,
|
|
298
304
|
{
|
|
299
305
|
keepTranscripts,
|
|
306
|
+
archiveTranscript,
|
|
300
307
|
},
|
|
301
308
|
);
|
|
302
309
|
const { dumpSnapshots, ...snapStore } = makeSnapStore(
|
|
@@ -306,6 +313,7 @@ export function makeSwingStore(dirPath, forceReset, options = {}) {
|
|
|
306
313
|
noteExport,
|
|
307
314
|
{
|
|
308
315
|
keepSnapshots,
|
|
316
|
+
archiveSnapshot,
|
|
309
317
|
},
|
|
310
318
|
);
|
|
311
319
|
const { dumpBundles, ...bundleStore } = makeBundleStore(
|
|
@@ -554,6 +562,7 @@ export function makeSwingStore(dirPath, forceReset, options = {}) {
|
|
|
554
562
|
getCurrentSpanBounds: transcriptStore.getCurrentSpanBounds,
|
|
555
563
|
addItem: transcriptStore.addItem,
|
|
556
564
|
readSpan: transcriptStore.readSpan,
|
|
565
|
+
stopUsingTranscript: transcriptStore.stopUsingTranscript,
|
|
557
566
|
deleteVatTranscripts: transcriptStore.deleteVatTranscripts,
|
|
558
567
|
};
|
|
559
568
|
|