From f9d1e694e0d31fcd87844ee98a514df2a563e292 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 2 Dec 2021 16:04:07 +0100 Subject: [PATCH] Examples and bug-fixes --- packages/core/examples/box-and-client.js | 107 ++++++++++---------- packages/core/examples/box-and-client.ts | 122 +++++++++++------------ packages/core/src/index.ts | 20 +++- packages/core/src/runtime/actor.ts | 75 ++++++++++++-- packages/core/src/runtime/api.ts | 7 -- packages/core/src/runtime/pattern.ts | 43 +++++++- packages/core/src/schemas.ts | 11 ++ packages/core/src/transport/relay.ts | 5 +- packages/core/stubs/crypto.js | 1 + 9 files changed, 249 insertions(+), 142 deletions(-) delete mode 100644 packages/core/src/runtime/api.ts create mode 100644 packages/core/src/schemas.ts diff --git a/packages/core/examples/box-and-client.js b/packages/core/examples/box-and-client.js index 9e432ce..cc94426 100644 --- a/packages/core/examples/box-and-client.js +++ b/packages/core/examples/box-and-client.js @@ -2,9 +2,12 @@ /// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones -const { bootModule, Dataspace, Skeleton, Ground, Record, Discard, Capture, Observe } = require('..'); -const __ = Discard._instance; -const _$ = Capture(__); +import { + Pattern as P, + Observe, fromObserve, + Record, + Actor, Dataspace, +} from '..'; const BoxState = Record.makeConstructor()(Symbol.for('BoxState'), ['value']); const SetBox = Record.makeConstructor()(Symbol.for('SetBox'), ['newValue']); @@ -13,63 +16,63 @@ const N = 100000; console.time('box-and-client-' + N.toString()); -function boot(thisFacet) { - thisFacet.spawn('box', function (thisFacet) { - thisFacet.declareField(this, 'value', 0); - thisFacet.addEndpoint(() => { - // console.log('recomputing published BoxState', this.value); - return { assertion: BoxState(this.value), analysis: null }; +Actor.boot(t => { + t.activeFacet.preventInertCheck(); + const ds = t.ref(new Dataspace()); + + t.spawn(t => { + t.activeFacet.actor.name = 'box'; + const boxValue = t.field(0, 'value'); + + t.assertDataflow(_t => { + // console.log('recomputing published BoxState', boxValue.value); + return { + target: ds, + assertion: BoxState(boxValue.value), + }; }); - thisFacet.addDataflow(() => { - // console.log('dataflow saw new value', this.value); - if (this.value === N) { - thisFacet.stop(() => { + + t.dataflow(t => { + // console.log('dataflow saw new value', boxValue.value); + if (boxValue.value === N) { + t.stop(t.activeFacet, _t => { console.log('terminated box root facet'); }); } }); - thisFacet.addEndpoint(() => { - let analysis = Skeleton.analyzeAssertion(SetBox(_$)); - analysis.callback = thisFacet.wrap((thisFacet, evt, vs) => { - if (evt === Skeleton.EventType.MESSAGE) { - thisFacet.scheduleScript(() => { - this.value = vs[0]; - // console.log('box updated value', vs[0]); - }); + + t.assert(ds, fromObserve(Observe({ + pattern: P.rec(SetBox.constructorInfo.label, P.bind()), + observer: t.ref({ + message(_t, [v]) { + boxValue.value = v; + // console.log('box updated value', v); } - }); - return { assertion: Observe(SetBox(_$)), analysis }; - }); + }) + }))); }); - thisFacet.spawn('client', function (thisFacet) { - thisFacet.addEndpoint(() => { - let analysis = Skeleton.analyzeAssertion(BoxState(_$)); - analysis.callback = thisFacet.wrap((thisFacet, evt, [v]) => { - if (evt === Skeleton.EventType.ADDED) { - thisFacet.scheduleScript(() => { - // console.log('client sending SetBox', v + 1); - thisFacet.send(SetBox(v + 1)); - }); + t.spawn(t => { + t.activeFacet.actor.name = 'client'; + + t.assert(ds, fromObserve(Observe({ + pattern: P.rec(BoxState.constructorInfo.label, P.bind()), + observer: t.ref({ + assert(t, [v], _handle) { + // console.log('client sending SetBox', v + 1); + t.message(ds, SetBox(v + 1)); } - }); - return { assertion: Observe(BoxState(_$)), analysis }; - }); - thisFacet.addEndpoint(() => { - let analysis = Skeleton.analyzeAssertion(BoxState(__)); - analysis.callback = thisFacet.wrap((thisFacet, evt, _vs) => { - if (evt === Skeleton.EventType.REMOVED) { - thisFacet.scheduleScript(() => { - console.log('box gone'); - }); + }) + }))); + + t.assert(ds, fromObserve(Observe({ + pattern: P.rec(BoxState.constructorInfo.label, P._), + observer: t.ref({ + retract(_t) { + console.log('box gone'); + console.timeEnd('box-and-client-' + N.toString()); } - }); - return { assertion: Observe(BoxState(__)), analysis }; - }); + }) + }))); }); - - thisFacet.actor.dataspace.ground().addStopHandler(() => - console.timeEnd('box-and-client-' + N.toString())); -} - -bootModule(boot); +}); diff --git a/packages/core/examples/box-and-client.ts b/packages/core/examples/box-and-client.ts index 79a47fd..c58c304 100644 --- a/packages/core/examples/box-and-client.ts +++ b/packages/core/examples/box-and-client.ts @@ -2,83 +2,77 @@ /// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones -import { bootModule, Skeleton, Record, Discard, Capture, Observe, Facet, Value } from '..'; -const __ = Discard._instance; -const _$ = Capture(__); +import { + Pattern as P, + Observe, fromObserve, + Record, + Actor, Dataspace, +} from '..'; -// The current pattern representation puts Capture and Discard record -// instances into Record fields, so those record fields have to be -// prepared to type them, which is why we see `number | Pattern` here -// rather than the ideal `number`. -// -type Pattern = ReturnType | ReturnType; -const BoxState = Record.makeConstructor<{value: number | Pattern}>()(Symbol.for('BoxState'), ['value']); -const SetBox = Record.makeConstructor<{newValue: number | Pattern}>()(Symbol.for('SetBox'), ['newValue']); +const BoxState = Record.makeConstructor<{value: number}>()(Symbol.for('BoxState'), ['value']); +const SetBox = Record.makeConstructor<{newValue: number}>()(Symbol.for('SetBox'), ['newValue']); const N = 100000; console.time('box-and-client-' + N.toString()); -function boot(thisFacet: Facet<{}>) { - thisFacet.spawn<{ value: number }>('box', function (thisFacet) { - thisFacet.declareField(this, 'value', 0); - thisFacet.addEndpoint(function () { - // console.log('recomputing published BoxState', this.value); - return { assertion: BoxState(this.value), analysis: null }; +Actor.boot(t => { + t.activeFacet.preventInertCheck(); + const ds = t.ref(new Dataspace()); + + t.spawn(t => { + t.activeFacet.actor.name = 'box'; + const boxValue = t.field(0, 'value'); + + t.assertDataflow(_t => { + // console.log('recomputing published BoxState', boxValue.value); + return { + target: ds, + assertion: BoxState(boxValue.value), + }; }); - thisFacet.addDataflow(function () { - // console.log('dataflow saw new value', this.value); - if (this.value === N) { - thisFacet.stop(function () { + + t.dataflow(t => { + // console.log('dataflow saw new value', boxValue.value); + if (boxValue.value === N) { + t.stop(t.activeFacet, _t => { console.log('terminated box root facet'); }); } }); - thisFacet.addEndpoint(function () { - let analysis = Skeleton.analyzeAssertion(SetBox(_$)); - analysis.callback = thisFacet.wrap(function (thisFacet, evt, [v]) { - if (evt === Skeleton.EventType.MESSAGE) { - if (typeof v !== 'number') return; - thisFacet.scheduleScript(function () { - this.value = v; - // console.log('box updated value', v); - }); + + t.assert(ds, fromObserve(Observe({ + pattern: P.rec(SetBox.constructorInfo.label, P.bind()), + observer: t.ref({ + message(_t, [v]: [number]) { + boxValue.value = v; + // console.log('box updated value', v); } - }); - return { assertion: Observe(SetBox(_$)), analysis }; - }); + }) + }))); }); - thisFacet.spawn('client', function (thisFacet: Facet<{}>) { - thisFacet.addEndpoint(function () { - let analysis = Skeleton.analyzeAssertion(BoxState(_$)); - analysis.callback = thisFacet.wrap(function (thisFacet, evt, [v]) { - if (evt === Skeleton.EventType.ADDED) { - if (typeof v !== 'number') return; - thisFacet.scheduleScript(function () { - // console.log('client sending SetBox', v + 1); - thisFacet.send(SetBox(v + 1)); - }); - } - }); - return { assertion: Observe(BoxState(_$)), analysis }; - }); - thisFacet.addEndpoint(function () { - let analysis = Skeleton.analyzeAssertion(BoxState(__)); - analysis.callback = thisFacet.wrap(function (thisFacet, evt, _vs) { - if (evt === Skeleton.EventType.REMOVED) { - thisFacet.scheduleScript(function () { - console.log('box gone'); - }); - } - }); - return { assertion: Observe(BoxState(__)), analysis }; - }); - }); + t.spawn(t => { + t.activeFacet.actor.name = 'client'; - thisFacet.actor.dataspace.ground().addStopHandler(function () { - console.timeEnd('box-and-client-' + N.toString()); - }); -} + t.assert(ds, fromObserve(Observe({ + pattern: P.rec(BoxState.constructorInfo.label, P.bind()), + observer: t.ref({ + assert(t, [v]: [number], _handle) { + // console.log('client sending SetBox', v + 1); + t.message(ds, SetBox(v + 1)); + } + }) + }))); -bootModule(boot); + t.assert(ds, fromObserve(Observe({ + pattern: P.rec(BoxState.constructorInfo.label, P._), + observer: t.ref({ + retract(_t) { + console.log('box gone'); + console.timeEnd('box-and-client-' + N.toString()); + } + }) + }))); + }); +}); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index bf12a01..4eca739 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -3,12 +3,24 @@ export * from '@preserves/core'; -export * from './runtime/randomid.js'; -export * from './runtime/bag.js'; -export * as API from './runtime/api.js'; -export * as Skeleton from './runtime/skeleton.js'; +export * as Schemas from './schemas.js'; +export { Observe, asObserve, toObserve, fromObserve } from './gen/dataspace.js'; +export * as DataspacePatterns from './gen/dataspacePatterns.js'; + export * from './runtime/actor.js'; +export * from './runtime/bag.js'; +export * as Dataflow from './runtime/dataflow.js'; export * from './runtime/dataspace.js'; +export * as Pattern from './runtime/pattern.js'; +export * from './runtime/randomid.js'; +export * as Rewrite from './runtime/rewrite.js'; +export * as Skeleton from './runtime/skeleton.js'; +export * as Task from './runtime/task.js'; + +export * as Cryptography from './transport/cryptography.js'; +export * as WireProtocol from './transport/protocol.js'; +export * as Relay from './transport/relay.js'; +export * as Sturdy from './transport/sturdy.js'; import { randomId } from './runtime/randomid.js'; diff --git a/packages/core/src/runtime/actor.ts b/packages/core/src/runtime/actor.ts index 4a4cd7a..709fd1d 100644 --- a/packages/core/src/runtime/actor.ts +++ b/packages/core/src/runtime/actor.ts @@ -55,13 +55,27 @@ export type DataflowBlock = (t: Turn) => void; export class Actor { readonly id = nextActorId++; + name: AnyValue = this.id; readonly root: Facet; _dataflowGraph: DataflowGraph | null = null; exitReason: ExitReason = null; readonly exitHooks: Array = []; - constructor(bootProc: LocalAction, initialAssertions: OutboundMap = new Map()) { + static boot(bootProc: LocalAction, initialAssertions: OutboundMap = new Map()): Actor { + const newActor = new Actor(initialAssertions); + newActor._boot(bootProc); + return newActor; + } + + static __unsafeNew(initialAssertions: OutboundMap = new Map()) { + return new Actor(initialAssertions); + } + + private constructor(initialAssertions: OutboundMap = new Map()) { this.root = new Facet(this, null, initialAssertions); + } + + _boot(bootProc: LocalAction) { Turn.for(new Facet(this, this.root), stopIfInertAfter(bootProc)); } @@ -81,7 +95,7 @@ export class Actor { if (this.exitReason !== null) return; this.exitReason = reason; if (!reason.ok) { - console.error(`Actor ${this.id} crashed:`, reason.err); + console.error(`${this} crashed:`, reason.err); } this.exitHooks.forEach(hook => hook(t)); queueTask(() => Turn.for(this.root, t => this.root._terminate(t, reason.ok), true)); @@ -91,6 +105,10 @@ export class Actor { if (this._dataflowGraph === null) return; this._dataflowGraph.repairDamage(block => block(t)); } + + toString(): string { + return `Actor(${this.name.asPreservesText()})`; + } } export class Facet { @@ -129,6 +147,12 @@ export class Facet { }; } + _halfLink(other: Facet): void { + const h = nextHandle++; + const e = { handle: h, peer: { relay: other, target: new StopOnRetract() }, established: true }; + this.outbound.set(h, e); + } + _terminate(t: Turn, orderly: boolean): void { if (!this.isLive) return; this.isLive = false; @@ -154,6 +178,24 @@ export class Facet { } }); } + + idChain(): string { + let facetIds = []; + for (let f: Facet | null = this; f !== null; f = f.parent) { + facetIds.push(f.id); + } + return facetIds.reverse().join(':') + ':' + this.actor.name.asPreservesText(); + } + + toString(): string { + return `Facet(#{this.idChain()})`; + } +} + +export class StopOnRetract implements Partial { + retract(turn: Turn, _handle: Handle): void { + turn.stop(); + } } export function _sync_impl(turn: Turn, e: Partial, peer: Ref): void { @@ -217,14 +259,27 @@ export class Turn { } spawn(bootProc: LocalAction, initialAssertions = new IdentitySet()): void { + this._spawn(bootProc, initialAssertions); + } + + _spawn(bootProc: LocalAction, initialAssertions = new IdentitySet()): Actor { + const newOutbound: OutboundMap = new Map(); + initialAssertions.forEach(key => newOutbound.set(key, this.activeFacet.outbound.get(key)!)); + // ^ we trust initialAssertions, so can use `!` safely + + const newActor = Actor.__unsafeNew(newOutbound); this.enqueue(this.activeFacet, () => { - const newOutbound: OutboundMap = new Map(); - initialAssertions.forEach(key => { - newOutbound.set(key, this.activeFacet.outbound.get(key)!); // we trust initialAssertions - this.activeFacet.outbound.delete(key); - }); - queueTask(() => new Actor(bootProc, newOutbound)); + initialAssertions.forEach(key => this.activeFacet.outbound.delete(key)); + queueTask(() => newActor._boot(bootProc)); }); + return newActor; + } + + spawnLink(bootProc: LocalAction, initialAssertions = new IdentitySet()): void { + if (!this.activeFacet.isLive) return; + const newActor = this._spawn(bootProc, initialAssertions); + this.activeFacet._halfLink(newActor.root); + newActor.root._halfLink(this.activeFacet); } stopActor(): void { @@ -317,11 +372,11 @@ export class Turn { if (a !== null) this.enqueue(ref.relay, t => ref.target.message?.(t, assertion)); } - enqueue(relay: Facet, a: LocalAction): void { + enqueue(relay: Facet, a0: LocalAction): void { if (this.queues === null) { throw new Error("Attempt to reuse a committed Turn"); } - a = t => t._inFacet(relay, a); + const a: LocalAction = t => t._inFacet(relay, a0); this.queues.get(relay.actor)?.push(a) ?? this.queues.set(relay.actor, [a]); } diff --git a/packages/core/src/runtime/api.ts b/packages/core/src/runtime/api.ts deleted file mode 100644 index 3c0a3bd..0000000 --- a/packages/core/src/runtime/api.ts +++ /dev/null @@ -1,7 +0,0 @@ -/// SPDX-License-Identifier: GPL-3.0-or-later -/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones - -// Keep these definitions in sync with internals.ts from the compiler package - -export type NonEmptySkeleton = { shape: Shape, members: Skeleton[] }; -export type Skeleton = null | NonEmptySkeleton; diff --git a/packages/core/src/runtime/pattern.ts b/packages/core/src/runtime/pattern.ts index 3882afd..898d4df 100644 --- a/packages/core/src/runtime/pattern.ts +++ b/packages/core/src/runtime/pattern.ts @@ -1,7 +1,7 @@ /// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones -import { canonicalString, is, Record, RecordConstructorInfo, Value } from '@preserves/core'; +import { canonicalString, is, KeyedDictionary, Record, RecordConstructorInfo, Value } from '@preserves/core'; import { AnyValue } from './actor.js'; import * as P from '../gen/dataspacePatterns.js'; @@ -146,3 +146,44 @@ export function withoutCaptures(p: P.Pattern): P.Pattern { } return walk(p); } + +//--------------------------------------------------------------------------- +// Constructor helpers + +export function bind(p?: P.Pattern): P.Pattern { + return P.Pattern.DBind(P.DBind(p ?? _)); +} + +export function discard(): P.Pattern { + return P.Pattern.DDiscard(P.DDiscard()); +} + +export const _ = discard(); + +export function lit(v: AnyValue): P.Pattern { + return P.Pattern.DLit(P.DLit(v)); +} + +export function rec(label: AnyValue, ... fields: P.Pattern[]): P.Pattern { + return P.Pattern.DCompound(P.DCompound.rec({ + ctor: P.CRec({ + label, + arity: fields.length, + }), + members: new KeyedDictionary(fields.map((p, i) => [i, p])), + })); +} + +export function arr(... patterns: P.Pattern[]): P.Pattern { + return P.Pattern.DCompound(P.DCompound.arr({ + ctor: P.CArr(patterns.length), + members: new KeyedDictionary(patterns.map((p, i) => [i, p])), + })); +} + +export function dict(... entries: [AnyValue, P.Pattern][]): P.Pattern { + return P.Pattern.DCompound(P.DCompound.dict({ + ctor: P.CDict(), + members: new KeyedDictionary(entries), + })); +} diff --git a/packages/core/src/schemas.ts b/packages/core/src/schemas.ts new file mode 100644 index 0000000..1e5cb08 --- /dev/null +++ b/packages/core/src/schemas.ts @@ -0,0 +1,11 @@ +export * as dataspacePatterns from './gen/dataspacePatterns.js'; +export * as dataspace from './gen/dataspace.js'; +export * as gatekeeper from './gen/gatekeeper.js'; +export * as protocol from './gen/protocol.js'; +export * as service from './gen/service.js'; +export * as stream from './gen/stream.js'; +export * as sturdy from './gen/sturdy.js'; +export * as tcp from './gen/tcp.js'; +export * as timer from './gen/timer.js'; +export * as transportAddress from './gen/transportAddress.js'; +export * as worker from './gen/worker.js'; diff --git a/packages/core/src/transport/relay.ts b/packages/core/src/transport/relay.ts index 4ada25a..2c83534 100644 --- a/packages/core/src/transport/relay.ts +++ b/packages/core/src/transport/relay.ts @@ -107,10 +107,7 @@ export class Membrane { } export const INERT_REF: Ref = { - relay: (() => { - const a = new Actor(t => t.stop()); - return a.root; - })(), + relay: Actor.boot(t => t.stop()).root, target: {}, }; diff --git a/packages/core/stubs/crypto.js b/packages/core/stubs/crypto.js index cb0c723..962688f 100644 --- a/packages/core/stubs/crypto.js +++ b/packages/core/stubs/crypto.js @@ -2,3 +2,4 @@ /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones export const randomBytes = void 0; +export const createHmac = void 0;