@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.
@@ -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, which wants to start from the contents of the export, rather than initializing a brand new (empty) kernel state.
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
- When starting a brand new instance, host applications would normally call `openSwingStore(dirPath)` to create a new (empty) SwingStore, then call SwingSet's `initializeSwingset(config, .., kernelStorage)` to let the kernel initialize the DB with a config-dependent starting state:
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 initialization step is replaced with `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:
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.commit();
192
- // now the swingstore is fully populated
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 use `makeSwingsetController()` to build a kernel that will start from the exported state.
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()` returns a swingstore, which means its options bag also contains the same options as `openSwingStore()`, including the `keepTranscripts` option. This defaults to `true`, but if it were overridden to `false`, then the new swingstore will delete transcript spans as soon as they are no longer needed for operational purposes (e.g. when `transcriptStore.rolloverSpan()` is called).
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
- * the `importSwingStore` call (and all subsequent `openSwingStore` calls) must not use `keepTranscripts: false`, otherwise the new swingstore will prune historical spans as new ones are created (during `rolloverSpan`).
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-cdd20f7.0+cdd20f7",
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-cdd20f7.0+cdd20f7",
25
- "@agoric/internal": "0.3.3-dev-cdd20f7.0+cdd20f7",
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": "cdd20f7407af2f09f93cfedc4d26966597ac70d2"
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 create a new swingStore from an object implementing the
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 store = makeSwingStore(dirPath, true, makeSwingStoreOptions);
31
- const { kernelStorage, internal } = store;
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 store;
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
- db.exec(`PRAGMA journal_mode=WAL`);
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({ snapStore, transcriptStore, bundleStore });
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);
@@ -118,7 +118,11 @@ test('b0 import', async t => {
118
118
  },
119
119
  close: async () => undefined,
120
120
  };
121
- const { kernelStorage } = await importSwingStore(exporter);
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);
@@ -322,6 +322,7 @@ async function testExportImport(
322
322
  }
323
323
  t.is(failureMode, 'none');
324
324
  const ssIn = await doImport();
325
+ t.teardown(ssIn.hostStorage.close);
325
326
  await ssIn.hostStorage.commit();
326
327
  let dumpsShouldMatch = true;
327
328
  if (runMode === 'operational') {
@@ -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 ss = await importSwingStore(exporter, dbDir);
23
- await ss.hostStorage.commit();
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 ss = await importSwingStore(exporter, dbDir);
80
- await ss.hostStorage.commit();
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 ss = await importSwingStore(exporter, dbDir);
99
- await ss.hostStorage.commit();
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 ss = await importSwingStore(exporter, dbDir);
119
- await ss.hostStorage.commit();
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
  });