@daugasauron/dolly 0.1.5 → 0.1.6

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.
Files changed (76) hide show
  1. package/README.md +51 -78
  2. package/coi-serviceworker.js +23 -8
  3. package/dist/dolly-build-id.mjs +1 -1
  4. package/dist/dolly-image-build-id.mjs +1 -1
  5. package/dist/dolly-process-0.wasm +0 -0
  6. package/dist/dolly-process-abi.mjs +1 -1
  7. package/dist/dolly-process-dso.mjs +19 -11
  8. package/dist/dolly-process-worker.mjs +88 -33
  9. package/dist/dolly-runtime-worker.mjs +1544 -0
  10. package/dist/dolly.wasm +0 -0
  11. package/host/dso/dso.mjs +2 -2
  12. package/host/dso/module.json +4 -1
  13. package/host/http/abi.mjs +3 -1
  14. package/host/http/broker.mjs +17 -7
  15. package/host/http/http.mjs +19 -5
  16. package/host/http/module.json +3 -1
  17. package/host/http/policy.mjs +13 -6
  18. package/host/http/refusals.mjs +77 -0
  19. package/host/input/abi.mjs +1 -1
  20. package/host/modules.mjs +64 -23
  21. package/host/nena/module.json +2 -1
  22. package/host/nena/nena.mjs +2 -0
  23. package/host/packages/abi.mjs +27 -0
  24. package/host/packages/module.json +6 -4
  25. package/host/packages/packages.mjs +23 -8
  26. package/host/packages/service.mjs +46 -20
  27. package/host/packages/transport.mjs +123 -0
  28. package/host/packages/worker.mjs +13 -9
  29. package/host/requirements.mjs +1 -1
  30. package/host/runtime/abi.mjs +32 -0
  31. package/host/runtime/lease-bridge.mjs +13 -8
  32. package/host/runtime/module.json +3 -3
  33. package/host/runtime/runtime.mjs +3 -3
  34. package/host/snapshot/abi.mjs +2 -1
  35. package/host/snapshot/module.json +1 -1
  36. package/host/snapshot/snapshot.mjs +80 -57
  37. package/host/snapshot/transport.mjs +10 -3
  38. package/host/snapshot/ui.mjs +64 -39
  39. package/host/stack/module.json +1 -0
  40. package/host/stack/stack.mjs +1 -1
  41. package/host/threads/module.json +6 -1
  42. package/host/threads/threads.mjs +32 -13
  43. package/package.json +2 -2
  44. package/{src → page}/browser.mjs +37 -23
  45. package/page/generated.mjs +8 -0
  46. package/{src → page}/image-entry.mjs +10 -6
  47. package/{src → page}/image-inputs.mjs +11 -0
  48. package/{src → page}/image-names.mjs +2 -2
  49. package/{src → page}/kernel-plugin.mjs +1 -1
  50. package/{src → page}/pack-store.mjs +1 -1
  51. package/{src → page}/packaged-snapshot.mjs +56 -46
  52. package/{src → page}/process-constants.mjs +72 -15
  53. package/{src → page}/process-supervisor.mjs +155 -227
  54. package/{src → page}/snapshot-records.mjs +63 -44
  55. package/{src → page}/static-asset.mjs +36 -20
  56. package/{src → page}/version.mjs +1 -1
  57. package/{src → page}/worker-image.mjs +20 -9
  58. package/{src → site}/build-worker.mjs +36 -25
  59. package/{src → site}/custom-image.mjs +2 -10
  60. package/{src → site}/dollyfile-graph.mjs +1 -1
  61. package/{src → site}/dollyfile-view.mjs +1 -1
  62. package/{src → site}/image-artifact.mjs +9 -8
  63. package/{src → site}/image-build.mjs +4 -4
  64. package/{src → site}/image-builder.mjs +5 -4
  65. package/site/offline.mjs +53 -0
  66. package/{src → site}/session-store.mjs +47 -38
  67. package/src/runtime-worker.mjs +0 -57
  68. /package/{src → page}/build-log.mjs +0 -0
  69. /package/{src → page}/images.mjs +0 -0
  70. /package/{src → page}/page-chords.mjs +0 -0
  71. /package/{src → page}/page-indicators.mjs +0 -0
  72. /package/{src → page}/process-abi.mjs +0 -0
  73. /package/{src → page}/sha256.mjs +0 -0
  74. /package/{src → page}/terminal-text.mjs +0 -0
  75. /package/{src → page}/wasm-interface.mjs +0 -0
  76. /package/{src → site}/alphabetical.mjs +0 -0
@@ -1,11 +1,13 @@
1
1
  import { alphabetical } from "./alphabetical.mjs";
2
- import { validName } from "./image-names.mjs";
3
- import { imageInputs } from "./image-inputs.mjs";
4
- import { MAX_SNAPSHOT_BYTES } from "./snapshot-records.mjs";
5
- import { DOLLY_VERSION } from "./version.mjs";
6
-
7
- export const DOLLY_SESSION_FORMAT_VERSION = 3;
8
- export const DOLLY_SESSION_MAX_BYTES = 512 * 1024 * 1024;
2
+ import { validName } from "../page/image-names.mjs";
3
+ import { imageInputs } from "../page/image-inputs.mjs";
4
+ import { MAX_SNAPSHOT_BYTES } from "../page/snapshot-records.mjs";
5
+ import { DOLLY_VERSION } from "../page/version.mjs";
6
+
7
+ // A save is the whole filesystem but /dev, /proc and /run (host/snapshot/kernel.c),
8
+ // at most 8 GiB before and after compression.
9
+ export const DOLLY_SESSION_FORMAT_VERSION = 4;
10
+ export const DOLLY_SESSION_MAX_BYTES = 8 * 1024 * 1024 * 1024;
9
11
  export const DOLLY_SESSION_METADATA_MAX_BYTES = 1024 * 1024;
10
12
 
11
13
  // Saves belong to a version: every version's pages open their own database.
@@ -43,16 +45,17 @@ export function customSessionIdentity(custom) {
43
45
  return `custom:${artifact.recipeSha256}:${artifact.sha256}`;
44
46
  }
45
47
 
46
- export function sessionCompatible(record, definitions, buildId, imageBuildId) {
48
+ // A save holds its whole filesystem, so it opens wherever its programs run: on
49
+ // a runtime with the image build ID of the one that made it (buildId; a rebuilt
50
+ // kernel keeps it while the images stay valid), whatever became of its image's
51
+ // recipe. The image need only still be named (its host modules are the
52
+ // catalog's), a custom one by the recipe it carries.
53
+ export function sessionCompatible(record, definitions, buildId) {
47
54
  if (record.formatVersion !== DOLLY_SESSION_FORMAT_VERSION || record.buildId !== buildId) return false;
48
55
  if (record.image === "custom") {
49
- try {
50
- return record.imageIdentity === customSessionIdentity(record.customImage) &&
51
- record.customImage.artifact.buildId === imageBuildId;
52
- } catch { return false; }
56
+ try { return record.imageIdentity === customSessionIdentity(record.customImage); } catch { return false; }
53
57
  }
54
- return record.customImage === undefined && definitions.some(({ image }) => image === record.image) &&
55
- record.imageIdentity === sessionImageIdentity(definitions, record.image);
58
+ return record.customImage === undefined && definitions.some(({ image }) => image === record.image);
56
59
  }
57
60
 
58
61
  export function sessionLoadUrl(name, applicationBase) {
@@ -92,16 +95,36 @@ async function collectStream(stream, maximum) {
92
95
  }
93
96
  }
94
97
 
98
+ // The compressed save, a Blob of its parts: no buffer holds it whole.
99
+ async function collectBlob(stream, maximum) {
100
+ const reader = stream.getReader(), parts = [];
101
+ let length = 0;
102
+ try {
103
+ for (;;) {
104
+ const { done, value } = await reader.read();
105
+ if (done) break;
106
+ length += value.byteLength;
107
+ if (length > maximum) throw new Error("Dolly session exceeds its size limit");
108
+ parts.push(value);
109
+ }
110
+ return new Blob(parts);
111
+ } catch (error) {
112
+ await reader.cancel(error).catch(() => {});
113
+ throw error;
114
+ } finally {
115
+ reader.releaseLock();
116
+ }
117
+ }
118
+
95
119
  export async function encodeSessionStream(produce) {
96
120
  const stream = new CompressionStream("gzip"), writer = stream.writable.getWriter();
97
- const encoded = collectStream(stream.readable, DOLLY_SESSION_MAX_BYTES);
121
+ const encoded = collectBlob(stream.readable, DOLLY_SESSION_MAX_BYTES);
98
122
  // The reader can fail while the producer is awaiting its next mailbox chunk.
99
123
  encoded.catch(() => {});
100
124
  try {
101
125
  await produce(bytes => writer.write(bytes));
102
126
  await writer.close();
103
- // Blob storage needs fixed buffers; decoded buffers transfer directly to Wasm.
104
- return { encoding: "gzip", bytes: (await encoded).transferToFixedLength() };
127
+ return { encoding: "gzip", bytes: await encoded };
105
128
  } catch (error) {
106
129
  await writer.abort(error).catch(() => {});
107
130
  await encoded.catch(() => {});
@@ -109,22 +132,10 @@ export async function encodeSessionStream(produce) {
109
132
  }
110
133
  }
111
134
 
112
- export async function decodeSessionSnapshot(record) {
113
- if (record === null || typeof record !== "object" ||
114
- !(record.bytes instanceof ArrayBuffer) || record.bytes.byteLength === 0 ||
115
- record.bytes.byteLength > DOLLY_SESSION_MAX_BYTES || record.encoding !== "gzip") {
116
- throw new Error("Stored Dolly session is invalid");
117
- }
118
- let offset = 0;
119
- const stream = new ReadableStream({
120
- pull(controller) {
121
- if (offset === record.bytes.byteLength) { controller.close(); return; }
122
- const length = Math.min(65536, record.bytes.byteLength - offset);
123
- controller.enqueue(new Uint8Array(record.bytes, offset, length));
124
- offset += length;
125
- },
126
- }).pipeThrough(new DecompressionStream("gzip"));
127
- return collectStream(stream, DOLLY_SESSION_MAX_BYTES);
135
+ // A whole save in one buffer, for a test that reads its records: at most 2 GiB.
136
+ export async function decodeSessionSnapshot(bytes) {
137
+ if (!(bytes instanceof Blob) || bytes.size === 0) throw new Error("Stored Dolly session is invalid");
138
+ return collectStream(bytes.stream().pipeThrough(new DecompressionStream("gzip")), 2 * 1024 * 1024 * 1024);
128
139
  }
129
140
 
130
141
  function openDatabase() {
@@ -164,7 +175,6 @@ export async function loadStoredSession(name) {
164
175
  if (!(record.bytes instanceof Blob) || record.bytes.size === 0 || record.bytes.size > DOLLY_SESSION_MAX_BYTES) {
165
176
  throw new Error("Stored Dolly session is invalid");
166
177
  }
167
- record.bytes = await record.bytes.arrayBuffer();
168
178
  return record;
169
179
  }
170
180
 
@@ -176,8 +186,8 @@ export function validateSessionRecord(record) {
176
186
  typeof record.image !== "string" || !validName(record.image) ||
177
187
  typeof record.imageIdentity !== "string" || record.imageIdentity.length > 256 ||
178
188
  !Number.isSafeInteger(record.updatedAt) ||
179
- !(record.bytes instanceof ArrayBuffer) || record.bytes.byteLength === 0 ||
180
- record.bytes.byteLength > DOLLY_SESSION_MAX_BYTES || record.encoding !== "gzip") {
189
+ !(record.bytes instanceof Blob) || record.bytes.size === 0 ||
190
+ record.bytes.size > DOLLY_SESSION_MAX_BYTES || record.encoding !== "gzip") {
181
191
  throw new TypeError("invalid Dolly session record");
182
192
  }
183
193
  if (record.image === "custom" ? record.imageIdentity !== customSessionIdentity(record.customImage)
@@ -186,8 +196,7 @@ export function validateSessionRecord(record) {
186
196
 
187
197
  export async function saveStoredSession(record, { overwrite = true } = {}) {
188
198
  validateSessionRecord(record);
189
- const stored = { ...record, bytes: new Blob([record.bytes]) };
190
- await transaction("readwrite", (store) => overwrite ? store.put(stored) : store.add(stored));
199
+ await transaction("readwrite", (store) => overwrite ? store.put(record) : store.add(record));
191
200
  }
192
201
 
193
202
  export async function deleteStoredSession(name) {
@@ -1,57 +0,0 @@
1
- // The runtime Worker boot() starts: it restores an image of the image directory
2
- // from its packs into a new kernel and runs its ENTRY (worker-image.mjs).
3
- import { imageDefinition } from "./images.mjs";
4
- import { loadPackagedSnapshotMetadata, streamPackagedSystemSnapshot, verifyVisibleRecipes } from "./packaged-snapshot.mjs";
5
- import { siteReference, hex } from "./static-asset.mjs";
6
- import { checkedMemoryRange, runImage } from "./worker-image.mjs";
7
-
8
- await runImage(async ({ image, files: given = {} }, host) => {
9
- const definition = imageDefinition(image);
10
- if (!definition) throw new Error("invalid Dolly image selection");
11
- const [metadata] = await Promise.all([loadPackagedSnapshotMetadata(image), verifyVisibleRecipes(image)]);
12
- host.require(definition.hostRequirements);
13
- host.admit(definition.hostRequirements);
14
- return {
15
- name: image,
16
- prepare({ files }) {
17
- files.write("/etc/dolly/recipe.locator", siteReference(definition.dollyfile));
18
- // The kernel restores a snapshot against the list of its paths.
19
- files.write("/etc/dolly/image.manifest", `${metadata.manifest.join("\n")}\n`);
20
- },
21
- async restore({ kernel, memory, files, stage }) {
22
- stage("loading precompiled userspace snapshot...");
23
- const capacity = 1024 * 1024;
24
- const address = kernel.dolly_snapshot_restore_address(BigInt(capacity));
25
- const staging = () => {
26
- const range = checkedMemoryRange(memory, address, capacity);
27
- return new Uint8Array(memory.buffer, range.address, range.size);
28
- };
29
- staging().set(Uint8Array.from(metadata.sha256.match(/../g), byte => parseInt(byte, 16)));
30
- if (kernel.dolly_bootstrap_snapshot_begin(BigInt(metadata.byteLength)) !== 0)
31
- throw new Error("Dolly snapshot stream initialization failed");
32
- let loaded = 0, reported = 0;
33
- await streamPackagedSystemSnapshot(metadata, bytes => {
34
- for (let offset = 0; offset < bytes.length; offset += capacity) {
35
- const chunk = bytes.subarray(offset, offset + capacity);
36
- staging().set(chunk);
37
- if (kernel.dolly_snapshot_stream_write(BigInt(chunk.length), 0) !== 0)
38
- throw new Error("Dolly rejected a snapshot stream record");
39
- }
40
- loaded += bytes.length;
41
- if (performance.now() - reported >= 1000) {
42
- const mib = 1024 * 1024;
43
- stage(`loading userspace: ${Math.min(Math.floor(loaded / mib), Math.ceil(metadata.byteLength / mib))} / ${Math.ceil(metadata.byteLength / mib)} MiB`);
44
- reported = performance.now();
45
- }
46
- }, () => {
47
- if (kernel.dolly_snapshot_stream_write(0n, 1) !== 0)
48
- throw new Error("Dolly rejected an incomplete snapshot part");
49
- return hex(staging().subarray(0, 32));
50
- });
51
- const status = kernel.dolly_bootstrap_snapshot_end();
52
- // The embedding's files (boot's `files`) lie over the image's.
53
- if (status === 0) for (const [path, contents] of Object.entries(given)) files.write(path, contents);
54
- return { status };
55
- },
56
- };
57
- });
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes