beam.com 0.1.0-rc.2 → 0.1.0-rc.3
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/README.md +5 -3
- package/js/beam.com.mjs +37 -6
- package/js/download.mjs +31 -4
- package/js/node.mjs +8 -3
- package/js/wrangler.mjs +47 -0
- package/package.json +1 -1
- package/runtime/beam.mjs +1 -1
- package/runtime/beam.wasm +0 -0
- package/runtime/browser.js +8 -7
- package/runtime/cloudflare/durable.js +32 -2
- package/runtime/cloudflare/index.js +15 -4
- package/runtime/cloudflare/worker.js +907 -83
- package/runtime/deno/worker.js +907 -83
- package/runtime/deno.js +2 -0
- package/runtime/durable.js +32 -2
- package/runtime/global.js +6 -3
- package/runtime/page/beam.mjs +1 -1
- package/runtime/page/browser.js +8 -7
- package/runtime/page/runtime-id.js +1 -1
- package/runtime/page/worker.js +907 -83
- package/runtime/release.json +1 -1
- package/runtime/runtime-id.js +1 -1
- package/runtime/worker.js +907 -83
package/runtime/beam.wasm
CHANGED
|
Binary file
|
package/runtime/browser.js
CHANGED
|
@@ -86,14 +86,15 @@ if (globalThis.caches && !caches.default) {
|
|
|
86
86
|
|
|
87
87
|
// A request to the app. A Request of a page drops the headers Upgrade and
|
|
88
88
|
// Sec-*, so the request is an object with the fields that worker.js reads,
|
|
89
|
-
// and a Headers of its own.
|
|
89
|
+
// and a Headers of its own. The body is a stream, as the body of a Request
|
|
90
|
+
// (bridgeUpload of worker.js reads it in parts), with its size.
|
|
90
91
|
function request(url, { method = 'GET', headers = {}, body = null } = {}) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
};
|
|
92
|
+
const h = new Headers(headers);
|
|
93
|
+
if (body != null && !h.has('content-length')) {
|
|
94
|
+
const size = typeof body === 'string' ? new TextEncoder().encode(body).length : body.byteLength ?? body.size;
|
|
95
|
+
if (size !== undefined) h.set('content-length', String(size));
|
|
96
|
+
}
|
|
97
|
+
return { url: url.href, method, headers: h, body: body == null ? null : new NativeResponse(body).body };
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
// A reader of the bytes of the file of url (app-com.js): one range for each
|
|
@@ -44,6 +44,7 @@ export class Beam extends DurableObject {
|
|
|
44
44
|
super(ctx, env);
|
|
45
45
|
this.vm = null;
|
|
46
46
|
this.expires = undefined;
|
|
47
|
+
this.resetting = false;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
// The VM starts at the first request: a path tenant gives its name there.
|
|
@@ -55,6 +56,10 @@ export class Beam extends DurableObject {
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
async fetch(request) {
|
|
59
|
+
if (this.resetting) {
|
|
60
|
+
return new Response('The app stopped. Try again.\n',
|
|
61
|
+
{ status: 503, headers: { 'content-type': 'text/plain', 'retry-after': '1' } });
|
|
62
|
+
}
|
|
58
63
|
if (this.env.BEAM_INSTANCES && this.env.BEAM_TENANTS === 'path') {
|
|
59
64
|
this.expires ??= (await this.ctx.storage.get(EXPIRES)) ?? null;
|
|
60
65
|
if (!this.expires || this.expires <= Date.now()) {
|
|
@@ -71,11 +76,36 @@ export class Beam extends DurableObject {
|
|
|
71
76
|
vars.BEAM_TENANT_PATH = `/t/${tenant}`;
|
|
72
77
|
}
|
|
73
78
|
if (this.expires) vars.BEAM_INSTANCE_EXPIRES = String(Math.floor(this.expires / 1000));
|
|
74
|
-
this.vm = this.makeVm(vars, new URL(request.url).hostname);
|
|
79
|
+
const vm = this.vm = this.makeVm(vars, new URL(request.url).hostname);
|
|
80
|
+
vm.onDead = () => this.vmStopped(vm);
|
|
81
|
+
vm.ready.catch(() => this.vmStopped(vm));
|
|
75
82
|
}
|
|
76
83
|
return this.vm.fetch(request);
|
|
77
84
|
}
|
|
78
85
|
|
|
86
|
+
// The VM stopped (erlang:halt, or a trap such as an allocation that
|
|
87
|
+
// failed), or its boot failed. Its open requests got 503. The memory of
|
|
88
|
+
// WebAssembly does not shrink, so the object resets after these answers:
|
|
89
|
+
// a new instance of the object, with a new VM, takes the next request.
|
|
90
|
+
vmStopped(vm) {
|
|
91
|
+
if (this.vm !== vm) return;
|
|
92
|
+
this.vm = null;
|
|
93
|
+
this.resetting = true;
|
|
94
|
+
setTimeout(() => this.reset('the VM stopped'), 100);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// A new instance of the object. When the host cannot abort the object,
|
|
98
|
+
// the next request starts a new VM in this instance: no request stays
|
|
99
|
+
// at 503.
|
|
100
|
+
reset(reason) {
|
|
101
|
+
try {
|
|
102
|
+
this.ctx.abort(reason);
|
|
103
|
+
} catch (e) {
|
|
104
|
+
console.log(`beam: the object did not reset (${e.message}): the next request starts a new VM`);
|
|
105
|
+
this.resetting = false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
79
109
|
// An instance: its time limit (from the registry).
|
|
80
110
|
async start(expires) {
|
|
81
111
|
await this.ctx.storage.put(EXPIRES, expires);
|
|
@@ -92,7 +122,7 @@ export class Beam extends DurableObject {
|
|
|
92
122
|
async expire() {
|
|
93
123
|
this.expires = null;
|
|
94
124
|
await this.ctx.storage.deleteAll();
|
|
95
|
-
if (this.vm) setTimeout(() => this.
|
|
125
|
+
if (this.vm) setTimeout(() => this.reset('the instance expired'), 0);
|
|
96
126
|
}
|
|
97
127
|
|
|
98
128
|
// The registry (the object ".registry") admits a visitor: a ticket and a
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
import { env as globalEnv } from 'cloudflare:workers';
|
|
28
28
|
import { release, use } from './release.js';
|
|
29
29
|
import { useSnapshot } from './snapshot.js';
|
|
30
|
-
import plain, {
|
|
30
|
+
import plain, { parseSnapshot, staticResponse, Vm } from './worker.js';
|
|
31
31
|
import front from './durable.js';
|
|
32
32
|
|
|
33
33
|
export { Beam } from './durable.js';
|
|
@@ -62,7 +62,9 @@ export { Beam } from './durable.js';
|
|
|
62
62
|
// --snapshot app.com, imported as a Data module, as app.com):
|
|
63
63
|
// - a full one (--full): each new VM restores it in place of a boot. A
|
|
64
64
|
// stateless Worker restores its VM in the global scope, before the
|
|
65
|
-
// first request
|
|
65
|
+
// first request. The global scope cannot inflate the pages of a
|
|
66
|
+
// snapshot in gzip (BEAMSNZ1), so such a snapshot restores in the first
|
|
67
|
+
// request;
|
|
66
68
|
// - one at the boot point: a new VM restores it only when the store of
|
|
67
69
|
// snapshots has none (the first VM of a deploy), and then the VM makes
|
|
68
70
|
// its snapshot for the store, as with no snapshot of the build.
|
|
@@ -80,12 +82,21 @@ export function serve(app, { binding = 'BEAM', name, nifs = null, snapshot = nul
|
|
|
80
82
|
// The VM of a stateless Worker, restored from a full snapshot in the
|
|
81
83
|
// global scope.
|
|
82
84
|
let vm = null;
|
|
83
|
-
const
|
|
85
|
+
const parsed = snapshot && parseSnapshot(snapshot);
|
|
86
|
+
const full = !!parsed && !parsed.boot_point && !parsed.packed;
|
|
84
87
|
const ready = full && !globalEnv[binding] ? (async () => {
|
|
85
88
|
vm = new Vm(globalEnv, { release: await release(), snapshot });
|
|
89
|
+
// A VM that stopped: the requests then go to the Worker of worker.js,
|
|
90
|
+
// which boots a VM of its own.
|
|
91
|
+
vm.onDead = () => { vm = null; };
|
|
86
92
|
await vm.ready;
|
|
87
93
|
if (globalEnv.BEAM_WARM) await vm.warm(globalEnv.BEAM_WARM);
|
|
88
|
-
})()
|
|
94
|
+
})().catch((e) => {
|
|
95
|
+
// A VM that did not restore: the same, and the entry still loads
|
|
96
|
+
// (await beam.ready).
|
|
97
|
+
console.log(`beam: the snapshot of the build did not restore in the global scope (${e?.message ?? e}): each request boots a VM`);
|
|
98
|
+
vm = null;
|
|
99
|
+
}) : Promise.resolve();
|
|
89
100
|
// The env of the front: the binding as BEAM, the name of the object, and
|
|
90
101
|
// the static files of app.com (the front serves them, with no request
|
|
91
102
|
// to the object).
|