@agoric/swing-store 0.9.2-dev-cdd20f7.0 → 0.9.2-dev-7244c71.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/docs/data-export.md +10 -20
- package/package.json +4 -4
- package/src/importer.js +14 -6
- package/src/swingStore.js +27 -3
- package/test/test-bundles.js +5 -1
- package/test/test-exportImport.js +1 -0
- package/test/test-import.js +2 -0
- package/test/test-repair-metadata.js +23 -9
package/docs/data-export.md
CHANGED
|
@@ -152,21 +152,9 @@ for (const name of exporter.getArtifactNames()) {
|
|
|
152
152
|
|
|
153
153
|
## Import
|
|
154
154
|
|
|
155
|
-
On other end of the export process is an importer. This is a new host application
|
|
155
|
+
On the other end of the export process is an importer. This is used to restore kernel state, so that a new host application can simply continue mostly as if it had been previously executing. The expectation is that the import and the execution are 2 independent events, and the execution doesn't need to be aware it was imported.
|
|
156
156
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
```js
|
|
160
|
-
// this is done only the first time an instance is created:
|
|
161
|
-
|
|
162
|
-
import { openSwingStore } from '@agoric/swing-store';
|
|
163
|
-
import { initializeSwingset } from '@agoric/swingset-vat';
|
|
164
|
-
const dirPath = './swing-store';
|
|
165
|
-
const { hostStorage, kernelStorage } = openSwingStore(dirPath);
|
|
166
|
-
await initializeSwingset(config, argv, kernelStorage);
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
Once the initial state is created, each time the application is launched, it will build a controller around the existing state:
|
|
157
|
+
For reference, after the initial state is created, each time the application is launched, it builds a controller around the existing state:
|
|
170
158
|
|
|
171
159
|
```js
|
|
172
160
|
import { openSwingStore } from '@agoric/swing-store';
|
|
@@ -177,7 +165,7 @@ const controller = await makeSwingsetController(kernelStorage);
|
|
|
177
165
|
// ... now do things like controller.run(), etc
|
|
178
166
|
```
|
|
179
167
|
|
|
180
|
-
When cloning an existing kernel, the
|
|
168
|
+
When cloning an existing kernel, the host application first imports and commits the restored state using `importSwingStore`. The host application should feed the importer with the export data and artifacts, by passing an object that has the same API as the SwingStore's exporter:
|
|
181
169
|
|
|
182
170
|
```js
|
|
183
171
|
import { importSwingStore } from '@agoric/swing-store';
|
|
@@ -188,11 +176,13 @@ const exporter = {
|
|
|
188
176
|
getArtifact(name) { // return blob of artifact data },
|
|
189
177
|
};
|
|
190
178
|
const { hostStorage } = importSwingStore(exporter, dirPath);
|
|
191
|
-
hostStorage
|
|
192
|
-
|
|
179
|
+
// Update any hostStorage as needed
|
|
180
|
+
await hostStorage.commit();
|
|
181
|
+
await hostStorage.close();
|
|
182
|
+
// now the populated swingstore can be re-opened using `openSwingStore``
|
|
193
183
|
```
|
|
194
184
|
|
|
195
|
-
Once the new SwingStore is fully populated with the previously-exported data, the host application can
|
|
185
|
+
Once the new SwingStore is fully populated with the previously-exported data, the host application can update any host specific state before committing and closing the SwingStore. `importSwingStore` returns only the host facet of the SwingStore instance, as it is not suitable for immediate execution.
|
|
196
186
|
|
|
197
187
|
## Optional / Historical Data
|
|
198
188
|
|
|
@@ -223,14 +213,14 @@ Also note that when a vat is terminated, we delete all information about it, inc
|
|
|
223
213
|
|
|
224
214
|
When importing, the `importSwingStore()` function's options bag takes a property named `artifactMode`, with the same meanings as for export. Importing with the `operational` mode will ignore any artifacts other than those needed for current operations, and will fail unless all such artifacts were available. Importing with `replay` will ignore spans from old incarnations, but will fail unless all spans from current incarnations are present. Importing with `archival` will fail unless all spans from all incarnations are present. There is no `debug` option during import.
|
|
225
215
|
|
|
226
|
-
`importSwingStore()`
|
|
216
|
+
While `importSwingStore()`'s options bag accepts the same options as `openSwingStore()`, since it returns only the host facet of a SwingStore, some of these options might not be meaningful, such as `keepTranscripts`.
|
|
227
217
|
|
|
228
218
|
So, to avoid pruning current-incarnation historical transcript spans when exporting from one swingstore to another, you must set (or avoid overriding) the following options along the way:
|
|
229
219
|
|
|
230
220
|
* the original swingstore must not be opened with `{ keepTranscripts: false }`, otherwise the old spans will be pruned immediately
|
|
231
221
|
* the export must use `makeSwingStoreExporter(dirpath, { artifactMode: 'replay'})`, otherwise the export will omit the old spans
|
|
232
222
|
* the import must use `importSwingStore(exporter, dirPath, { artifactMode: 'replay'})`, otherwise the import will ignore the old spans
|
|
233
|
-
*
|
|
223
|
+
* subsequent `openSwingStore` calls must not use `keepTranscripts: false`, otherwise the new swingstore will prune historical spans as new ones are created (during `rolloverSpan`).
|
|
234
224
|
|
|
235
225
|
## Implementation Details
|
|
236
226
|
|
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-7244c71.0+7244c71",
|
|
4
4
|
"description": "Persistent storage for SwingSet",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"lint:eslint": "eslint ."
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@agoric/assert": "0.6.1-dev-
|
|
25
|
-
"@agoric/internal": "0.3.3-dev-
|
|
24
|
+
"@agoric/assert": "0.6.1-dev-7244c71.0+7244c71",
|
|
25
|
+
"@agoric/internal": "0.3.3-dev-7244c71.0+7244c71",
|
|
26
26
|
"@endo/base64": "^0.2.34",
|
|
27
27
|
"@endo/bundle-source": "^2.7.0",
|
|
28
28
|
"@endo/check-bundle": "^0.2.21",
|
|
@@ -51,5 +51,5 @@
|
|
|
51
51
|
"typeCoverage": {
|
|
52
52
|
"atLeast": 75.52
|
|
53
53
|
},
|
|
54
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "7244c710962174a886903cf039c0fbe18874287f"
|
|
55
55
|
}
|
package/src/importer.js
CHANGED
|
@@ -11,14 +11,16 @@ import { assertComplete } from './assertComplete.js';
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Function used to
|
|
14
|
+
* Function used to populate a swingStore from an object implementing the
|
|
15
15
|
* exporter API. The exporter API may be provided by a swingStore instance, or
|
|
16
|
-
* implemented by a host to restore data that was previously exported.
|
|
16
|
+
* implemented by a host to restore data that was previously exported. The
|
|
17
|
+
* returned swingStore is not suitable for execution, and thus only contains
|
|
18
|
+
* the host facet for committing the populated swingStore.
|
|
17
19
|
*
|
|
18
20
|
* @param {import('./exporter').SwingStoreExporter} exporter
|
|
19
21
|
* @param {string | null} [dirPath]
|
|
20
22
|
* @param {ImportSwingStoreOptions} [options]
|
|
21
|
-
* @returns {Promise<import('./swingStore').SwingStore
|
|
23
|
+
* @returns {Promise<Pick<import('./swingStore').SwingStore, 'hostStorage' | 'debug'>>}
|
|
22
24
|
*/
|
|
23
25
|
export async function importSwingStore(exporter, dirPath = null, options = {}) {
|
|
24
26
|
if (dirPath && typeof dirPath !== 'string') {
|
|
@@ -27,8 +29,14 @@ export async function importSwingStore(exporter, dirPath = null, options = {}) {
|
|
|
27
29
|
const { artifactMode = 'operational', ...makeSwingStoreOptions } = options;
|
|
28
30
|
validateArtifactMode(artifactMode);
|
|
29
31
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
+
const { hostStorage, kernelStorage, internal, debug } = makeSwingStore(
|
|
33
|
+
dirPath,
|
|
34
|
+
true,
|
|
35
|
+
{
|
|
36
|
+
unsafeFastMode: true,
|
|
37
|
+
...makeSwingStoreOptions,
|
|
38
|
+
},
|
|
39
|
+
);
|
|
32
40
|
|
|
33
41
|
// For every exportData entry, we add a DB record. 'kv' entries are
|
|
34
42
|
// the "kvStore shadow table", and are not associated with any
|
|
@@ -121,5 +129,5 @@ export async function importSwingStore(exporter, dirPath = null, options = {}) {
|
|
|
121
129
|
assertComplete(internal, checkMode);
|
|
122
130
|
|
|
123
131
|
await exporter.close();
|
|
124
|
-
return
|
|
132
|
+
return { hostStorage, debug };
|
|
125
133
|
}
|
package/src/swingStore.js
CHANGED
|
@@ -203,9 +203,29 @@ export function makeSwingStore(dirPath, forceReset, options = {}) {
|
|
|
203
203
|
// mode that defers merge work for a later attempt rather than block any
|
|
204
204
|
// potential readers or writers. See https://sqlite.org/wal.html for details.
|
|
205
205
|
|
|
206
|
+
// However we also allow opening the DB with journaling off, which is unsafe
|
|
207
|
+
// and doesn't support rollback, but avoids any overhead for large
|
|
208
|
+
// transactions like for during an import.
|
|
209
|
+
|
|
210
|
+
function setUnsafeFastMode(enabled) {
|
|
211
|
+
const journalMode = enabled ? 'off' : 'wal';
|
|
212
|
+
const synchronousMode = enabled ? 'normal' : 'full';
|
|
213
|
+
!db.inTransaction || Fail`must not be in a transaction`;
|
|
214
|
+
|
|
215
|
+
db.unsafeMode(!!enabled);
|
|
216
|
+
// The WAL mode is persistent so it's not possible to switch to a different
|
|
217
|
+
// mode for an existing DB.
|
|
218
|
+
const actualMode = db.pragma(`journal_mode=${journalMode}`, {
|
|
219
|
+
simple: true,
|
|
220
|
+
});
|
|
221
|
+
actualMode === journalMode ||
|
|
222
|
+
filePath === ':memory:' ||
|
|
223
|
+
Fail`Couldn't set swing-store DB to ${journalMode} mode (is ${actualMode})`;
|
|
224
|
+
db.pragma(`synchronous=${synchronousMode}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
206
227
|
// PRAGMAs have to happen outside a transaction
|
|
207
|
-
|
|
208
|
-
db.exec(`PRAGMA synchronous=FULL`);
|
|
228
|
+
setUnsafeFastMode(options.unsafeFastMode);
|
|
209
229
|
|
|
210
230
|
// We use IMMEDIATE because the kernel is supposed to be the sole writer of
|
|
211
231
|
// the DB, and if some other process is holding a write lock, we want to find
|
|
@@ -481,7 +501,11 @@ export function makeSwingStore(dirPath, forceReset, options = {}) {
|
|
|
481
501
|
}
|
|
482
502
|
|
|
483
503
|
/** @type {import('./internal.js').SwingStoreInternal} */
|
|
484
|
-
const internal = harden({
|
|
504
|
+
const internal = harden({
|
|
505
|
+
snapStore,
|
|
506
|
+
transcriptStore,
|
|
507
|
+
bundleStore,
|
|
508
|
+
});
|
|
485
509
|
|
|
486
510
|
async function repairMetadata(exporter) {
|
|
487
511
|
return doRepairMetadata(internal, exporter);
|
package/test/test-bundles.js
CHANGED
|
@@ -118,7 +118,11 @@ test('b0 import', async t => {
|
|
|
118
118
|
},
|
|
119
119
|
close: async () => undefined,
|
|
120
120
|
};
|
|
121
|
-
const
|
|
121
|
+
const ss = await importSwingStore(exporter);
|
|
122
|
+
t.teardown(ss.hostStorage.close);
|
|
123
|
+
await ss.hostStorage.commit();
|
|
124
|
+
const serialized = ss.debug.serialize();
|
|
125
|
+
const { kernelStorage } = initSwingStore(null, { serialized });
|
|
122
126
|
const { bundleStore } = kernelStorage;
|
|
123
127
|
t.truthy(bundleStore.hasBundle(idA));
|
|
124
128
|
t.deepEqual(bundleStore.getBundle(idA), b0A);
|
package/test/test-import.js
CHANGED
|
@@ -46,6 +46,7 @@ test('import empty', async t => {
|
|
|
46
46
|
t.teardown(cleanup);
|
|
47
47
|
const exporter = makeExporter(new Map(), new Map());
|
|
48
48
|
const ss = await importSwingStore(exporter, dbDir);
|
|
49
|
+
t.teardown(ss.hostStorage.close);
|
|
49
50
|
await ss.hostStorage.commit();
|
|
50
51
|
const data = convert(ss.debug.dump());
|
|
51
52
|
t.deepEqual(data, {
|
|
@@ -69,6 +70,7 @@ const importTest = test.macro(async (t, mode) => {
|
|
|
69
70
|
|
|
70
71
|
// now import
|
|
71
72
|
const ss = await importSwingStore(exporter, dbDir, { artifactMode });
|
|
73
|
+
t.teardown(ss.hostStorage.close);
|
|
72
74
|
await ss.hostStorage.commit();
|
|
73
75
|
const data = convert(ss.debug.dump());
|
|
74
76
|
|
|
@@ -4,7 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import test from 'ava';
|
|
5
5
|
import sqlite3 from 'better-sqlite3';
|
|
6
6
|
|
|
7
|
-
import { importSwingStore } from '../src/index.js';
|
|
7
|
+
import { importSwingStore, openSwingStore } from '../src/index.js';
|
|
8
8
|
|
|
9
9
|
import { makeExporter, buildData } from './exports.js';
|
|
10
10
|
import { tmpDir } from './util.js';
|
|
@@ -19,8 +19,9 @@ test('repair metadata', async t => {
|
|
|
19
19
|
// then manually deleting the historical metadata entries from the
|
|
20
20
|
// DB
|
|
21
21
|
const exporter = makeExporter(exportData, artifacts);
|
|
22
|
-
const
|
|
23
|
-
await
|
|
22
|
+
const ssi = await importSwingStore(exporter, dbDir);
|
|
23
|
+
await ssi.hostStorage.commit();
|
|
24
|
+
await ssi.hostStorage.close();
|
|
24
25
|
|
|
25
26
|
const filePath = path.join(dbDir, 'swingstore.sqlite');
|
|
26
27
|
const db = sqlite3(filePath);
|
|
@@ -51,6 +52,8 @@ test('repair metadata', async t => {
|
|
|
51
52
|
t.deepEqual(ss2, [7]);
|
|
52
53
|
|
|
53
54
|
// now fix it
|
|
55
|
+
const ss = openSwingStore(dbDir);
|
|
56
|
+
t.teardown(ss.hostStorage.close);
|
|
54
57
|
await ss.hostStorage.repairMetadata(exporter);
|
|
55
58
|
await ss.hostStorage.commit();
|
|
56
59
|
|
|
@@ -62,6 +65,7 @@ test('repair metadata', async t => {
|
|
|
62
65
|
|
|
63
66
|
// repair should be idempotent
|
|
64
67
|
await ss.hostStorage.repairMetadata(exporter);
|
|
68
|
+
await ss.hostStorage.commit();
|
|
65
69
|
|
|
66
70
|
const ts4 = getTS.all('v1');
|
|
67
71
|
t.deepEqual(ts4, [0, 2, 5, 8]); // still there
|
|
@@ -76,11 +80,15 @@ test('repair metadata ignores kvStore entries', async t => {
|
|
|
76
80
|
const { exportData, artifacts } = buildData();
|
|
77
81
|
|
|
78
82
|
const exporter = makeExporter(exportData, artifacts);
|
|
79
|
-
const
|
|
80
|
-
await
|
|
83
|
+
const ssi = await importSwingStore(exporter, dbDir);
|
|
84
|
+
await ssi.hostStorage.commit();
|
|
85
|
+
await ssi.hostStorage.close();
|
|
81
86
|
|
|
82
87
|
// perform the repair with spurious kv entries
|
|
83
88
|
exportData.set('kv.key2', 'value2');
|
|
89
|
+
|
|
90
|
+
const ss = openSwingStore(dbDir);
|
|
91
|
+
t.teardown(ss.hostStorage.close);
|
|
84
92
|
await ss.hostStorage.repairMetadata(exporter);
|
|
85
93
|
await ss.hostStorage.commit();
|
|
86
94
|
|
|
@@ -95,14 +103,17 @@ test('repair metadata rejects mismatched snapshot entries', async t => {
|
|
|
95
103
|
const { exportData, artifacts } = buildData();
|
|
96
104
|
|
|
97
105
|
const exporter = makeExporter(exportData, artifacts);
|
|
98
|
-
const
|
|
99
|
-
await
|
|
106
|
+
const ssi = await importSwingStore(exporter, dbDir);
|
|
107
|
+
await ssi.hostStorage.commit();
|
|
108
|
+
await ssi.hostStorage.close();
|
|
100
109
|
|
|
101
110
|
// perform the repair with mismatched snapshot entry
|
|
102
111
|
const old = JSON.parse(exportData.get('snapshot.v1.4'));
|
|
103
112
|
const wrong = { ...old, hash: 'wrong' };
|
|
104
113
|
exportData.set('snapshot.v1.4', JSON.stringify(wrong));
|
|
105
114
|
|
|
115
|
+
const ss = openSwingStore(dbDir);
|
|
116
|
+
t.teardown(ss.hostStorage.close);
|
|
106
117
|
await t.throwsAsync(async () => ss.hostStorage.repairMetadata(exporter), {
|
|
107
118
|
message: /repairSnapshotRecord metadata mismatch/,
|
|
108
119
|
});
|
|
@@ -115,14 +126,17 @@ test('repair metadata rejects mismatched transcript span', async t => {
|
|
|
115
126
|
const { exportData, artifacts } = buildData();
|
|
116
127
|
|
|
117
128
|
const exporter = makeExporter(exportData, artifacts);
|
|
118
|
-
const
|
|
119
|
-
await
|
|
129
|
+
const ssi = await importSwingStore(exporter, dbDir);
|
|
130
|
+
await ssi.hostStorage.commit();
|
|
131
|
+
await ssi.hostStorage.close();
|
|
120
132
|
|
|
121
133
|
// perform the repair with mismatched transcript span entry
|
|
122
134
|
const old = JSON.parse(exportData.get('transcript.v1.0'));
|
|
123
135
|
const wrong = { ...old, hash: 'wrong' };
|
|
124
136
|
exportData.set('transcript.v1.0', JSON.stringify(wrong));
|
|
125
137
|
|
|
138
|
+
const ss = openSwingStore(dbDir);
|
|
139
|
+
t.teardown(ss.hostStorage.close);
|
|
126
140
|
await t.throwsAsync(async () => ss.hostStorage.repairMetadata(exporter), {
|
|
127
141
|
message: /repairTranscriptSpanRecord metadata mismatch/,
|
|
128
142
|
});
|