Examples and bug-fixes

This commit is contained in:
Tony Garnock-Jones 2021-12-02 16:04:07 +01:00
parent 0b746764dd
commit f9d1e694e0
9 changed files with 249 additions and 142 deletions

View File

@ -2,9 +2,12 @@
/// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com> /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
const { bootModule, Dataspace, Skeleton, Ground, Record, Discard, Capture, Observe } = require('..'); import {
const __ = Discard._instance; Pattern as P,
const _$ = Capture(__); Observe, fromObserve,
Record,
Actor, Dataspace,
} from '..';
const BoxState = Record.makeConstructor()(Symbol.for('BoxState'), ['value']); const BoxState = Record.makeConstructor()(Symbol.for('BoxState'), ['value']);
const SetBox = Record.makeConstructor()(Symbol.for('SetBox'), ['newValue']); const SetBox = Record.makeConstructor()(Symbol.for('SetBox'), ['newValue']);
@ -13,63 +16,63 @@ const N = 100000;
console.time('box-and-client-' + N.toString()); console.time('box-and-client-' + N.toString());
function boot(thisFacet) { Actor.boot(t => {
thisFacet.spawn('box', function (thisFacet) { t.activeFacet.preventInertCheck();
thisFacet.declareField(this, 'value', 0); const ds = t.ref(new Dataspace());
thisFacet.addEndpoint(() => {
// console.log('recomputing published BoxState', this.value); t.spawn(t => {
return { assertion: BoxState(this.value), analysis: null }; 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); t.dataflow(t => {
if (this.value === N) { // console.log('dataflow saw new value', boxValue.value);
thisFacet.stop(() => { if (boxValue.value === N) {
t.stop(t.activeFacet, _t => {
console.log('terminated box root facet'); console.log('terminated box root facet');
}); });
} }
}); });
thisFacet.addEndpoint(() => {
let analysis = Skeleton.analyzeAssertion(SetBox(_$)); t.assert(ds, fromObserve(Observe({
analysis.callback = thisFacet.wrap((thisFacet, evt, vs) => { pattern: P.rec(SetBox.constructorInfo.label, P.bind()),
if (evt === Skeleton.EventType.MESSAGE) { observer: t.ref({
thisFacet.scheduleScript(() => { message(_t, [v]) {
this.value = vs[0]; boxValue.value = v;
// console.log('box updated value', vs[0]); // console.log('box updated value', v);
});
} }
}); })
return { assertion: Observe(SetBox(_$)), analysis }; })));
});
}); });
thisFacet.spawn('client', function (thisFacet) { t.spawn(t => {
thisFacet.addEndpoint(() => { t.activeFacet.actor.name = 'client';
let analysis = Skeleton.analyzeAssertion(BoxState(_$));
analysis.callback = thisFacet.wrap((thisFacet, evt, [v]) => { t.assert(ds, fromObserve(Observe({
if (evt === Skeleton.EventType.ADDED) { pattern: P.rec(BoxState.constructorInfo.label, P.bind()),
thisFacet.scheduleScript(() => { observer: t.ref({
// console.log('client sending SetBox', v + 1); assert(t, [v], _handle) {
thisFacet.send(SetBox(v + 1)); // console.log('client sending SetBox', v + 1);
}); t.message(ds, SetBox(v + 1));
} }
}); })
return { assertion: Observe(BoxState(_$)), analysis }; })));
});
thisFacet.addEndpoint(() => { t.assert(ds, fromObserve(Observe({
let analysis = Skeleton.analyzeAssertion(BoxState(__)); pattern: P.rec(BoxState.constructorInfo.label, P._),
analysis.callback = thisFacet.wrap((thisFacet, evt, _vs) => { observer: t.ref({
if (evt === Skeleton.EventType.REMOVED) { retract(_t) {
thisFacet.scheduleScript(() => { console.log('box gone');
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);

View File

@ -2,83 +2,77 @@
/// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com> /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { bootModule, Skeleton, Record, Discard, Capture, Observe, Facet, Value } from '..'; import {
const __ = Discard._instance; Pattern as P,
const _$ = Capture(__); Observe, fromObserve,
Record,
Actor, Dataspace,
} from '..';
// The current pattern representation puts Capture and Discard record const BoxState = Record.makeConstructor<{value: number}>()(Symbol.for('BoxState'), ['value']);
// instances into Record fields, so those record fields have to be const SetBox = Record.makeConstructor<{newValue: number}>()(Symbol.for('SetBox'), ['newValue']);
// prepared to type them, which is why we see `number | Pattern` here
// rather than the ideal `number`.
//
type Pattern = ReturnType<typeof Capture> | ReturnType<typeof Discard>;
const BoxState = Record.makeConstructor<{value: number | Pattern}>()(Symbol.for('BoxState'), ['value']);
const SetBox = Record.makeConstructor<{newValue: number | Pattern}>()(Symbol.for('SetBox'), ['newValue']);
const N = 100000; const N = 100000;
console.time('box-and-client-' + N.toString()); console.time('box-and-client-' + N.toString());
function boot(thisFacet: Facet<{}>) { Actor.boot(t => {
thisFacet.spawn<{ value: number }>('box', function (thisFacet) { t.activeFacet.preventInertCheck();
thisFacet.declareField(this, 'value', 0); const ds = t.ref(new Dataspace());
thisFacet.addEndpoint(function () {
// console.log('recomputing published BoxState', this.value); t.spawn(t => {
return { assertion: BoxState(this.value), analysis: null }; t.activeFacet.actor.name = 'box';
const boxValue = t.field<number>(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); t.dataflow(t => {
if (this.value === N) { // console.log('dataflow saw new value', boxValue.value);
thisFacet.stop(function () { if (boxValue.value === N) {
t.stop(t.activeFacet, _t => {
console.log('terminated box root facet'); console.log('terminated box root facet');
}); });
} }
}); });
thisFacet.addEndpoint(function () {
let analysis = Skeleton.analyzeAssertion(SetBox(_$)); t.assert(ds, fromObserve(Observe({
analysis.callback = thisFacet.wrap(function (thisFacet, evt, [v]) { pattern: P.rec(SetBox.constructorInfo.label, P.bind()),
if (evt === Skeleton.EventType.MESSAGE) { observer: t.ref({
if (typeof v !== 'number') return; message(_t, [v]: [number]) {
thisFacet.scheduleScript(function () { boxValue.value = v;
this.value = v; // console.log('box updated value', v);
// console.log('box updated value', v);
});
} }
}); })
return { assertion: Observe(SetBox(_$)), analysis }; })));
});
}); });
thisFacet.spawn('client', function (thisFacet: Facet<{}>) { t.spawn(t => {
thisFacet.addEndpoint(function () { t.activeFacet.actor.name = 'client';
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 };
});
});
thisFacet.actor.dataspace.ground().addStopHandler(function () { t.assert(ds, fromObserve(Observe({
console.timeEnd('box-and-client-' + N.toString()); 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());
}
})
})));
});
});

View File

@ -3,12 +3,24 @@
export * from '@preserves/core'; export * from '@preserves/core';
export * from './runtime/randomid.js'; export * as Schemas from './schemas.js';
export * from './runtime/bag.js'; export { Observe, asObserve, toObserve, fromObserve } from './gen/dataspace.js';
export * as API from './runtime/api.js'; export * as DataspacePatterns from './gen/dataspacePatterns.js';
export * as Skeleton from './runtime/skeleton.js';
export * from './runtime/actor.js'; export * from './runtime/actor.js';
export * from './runtime/bag.js';
export * as Dataflow from './runtime/dataflow.js';
export * from './runtime/dataspace.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'; import { randomId } from './runtime/randomid.js';

View File

@ -55,13 +55,27 @@ export type DataflowBlock = (t: Turn) => void;
export class Actor { export class Actor {
readonly id = nextActorId++; readonly id = nextActorId++;
name: AnyValue = this.id;
readonly root: Facet; readonly root: Facet;
_dataflowGraph: DataflowGraph | null = null; _dataflowGraph: DataflowGraph | null = null;
exitReason: ExitReason = null; exitReason: ExitReason = null;
readonly exitHooks: Array<LocalAction> = []; readonly exitHooks: Array<LocalAction> = [];
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); this.root = new Facet(this, null, initialAssertions);
}
_boot(bootProc: LocalAction) {
Turn.for(new Facet(this, this.root), stopIfInertAfter(bootProc)); Turn.for(new Facet(this, this.root), stopIfInertAfter(bootProc));
} }
@ -81,7 +95,7 @@ export class Actor {
if (this.exitReason !== null) return; if (this.exitReason !== null) return;
this.exitReason = reason; this.exitReason = reason;
if (!reason.ok) { if (!reason.ok) {
console.error(`Actor ${this.id} crashed:`, reason.err); console.error(`${this} crashed:`, reason.err);
} }
this.exitHooks.forEach(hook => hook(t)); this.exitHooks.forEach(hook => hook(t));
queueTask(() => Turn.for(this.root, t => this.root._terminate(t, reason.ok), true)); 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; if (this._dataflowGraph === null) return;
this._dataflowGraph.repairDamage(block => block(t)); this._dataflowGraph.repairDamage(block => block(t));
} }
toString(): string {
return `Actor(${this.name.asPreservesText()})`;
}
} }
export class Facet { 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 { _terminate(t: Turn, orderly: boolean): void {
if (!this.isLive) return; if (!this.isLive) return;
this.isLive = false; 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<Entity> {
retract(turn: Turn, _handle: Handle): void {
turn.stop();
}
} }
export function _sync_impl(turn: Turn, e: Partial<Entity>, peer: Ref): void { export function _sync_impl(turn: Turn, e: Partial<Entity>, peer: Ref): void {
@ -217,14 +259,27 @@ export class Turn {
} }
spawn(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): void { spawn(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): void {
this._spawn(bootProc, initialAssertions);
}
_spawn(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): 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, () => { this.enqueue(this.activeFacet, () => {
const newOutbound: OutboundMap = new Map(); initialAssertions.forEach(key => this.activeFacet.outbound.delete(key));
initialAssertions.forEach(key => { queueTask(() => newActor._boot(bootProc));
newOutbound.set(key, this.activeFacet.outbound.get(key)!); // we trust initialAssertions
this.activeFacet.outbound.delete(key);
});
queueTask(() => new Actor(bootProc, newOutbound));
}); });
return newActor;
}
spawnLink(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): void {
if (!this.activeFacet.isLive) return;
const newActor = this._spawn(bootProc, initialAssertions);
this.activeFacet._halfLink(newActor.root);
newActor.root._halfLink(this.activeFacet);
} }
stopActor(): void { stopActor(): void {
@ -317,11 +372,11 @@ export class Turn {
if (a !== null) this.enqueue(ref.relay, t => ref.target.message?.(t, assertion)); 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) { if (this.queues === null) {
throw new Error("Attempt to reuse a committed Turn"); 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]); this.queues.get(relay.actor)?.push(a) ?? this.queues.set(relay.actor, [a]);
} }

View File

@ -1,7 +0,0 @@
/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
// Keep these definitions in sync with internals.ts from the compiler package
export type NonEmptySkeleton<Shape> = { shape: Shape, members: Skeleton<Shape>[] };
export type Skeleton<Shape> = null | NonEmptySkeleton<Shape>;

View File

@ -1,7 +1,7 @@
/// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com> /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
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 { AnyValue } from './actor.js';
import * as P from '../gen/dataspacePatterns.js'; import * as P from '../gen/dataspacePatterns.js';
@ -146,3 +146,44 @@ export function withoutCaptures(p: P.Pattern): P.Pattern {
} }
return walk(p); 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),
}));
}

View File

@ -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';

View File

@ -107,10 +107,7 @@ export class Membrane {
} }
export const INERT_REF: Ref = { export const INERT_REF: Ref = {
relay: (() => { relay: Actor.boot(t => t.stop()).root,
const a = new Actor(t => t.stop());
return a.root;
})(),
target: {}, target: {},
}; };

View File

@ -2,3 +2,4 @@
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com> /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
export const randomBytes = void 0; export const randomBytes = void 0;
export const createHmac = void 0;