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-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
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);
});

View File

@ -2,83 +2,77 @@
/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
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<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 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<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);
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());
}
})
})));
});
});

View File

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

View File

@ -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<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);
}
_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<Entity> {
retract(turn: Turn, _handle: Handle): void {
turn.stop();
}
}
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 {
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, () => {
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<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 {
@ -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]);
}

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-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 * 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),
}));
}

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 = {
relay: (() => {
const a = new Actor(t => t.stop());
return a.root;
})(),
relay: Actor.boot(t => t.stop()).root,
target: {},
};

View File

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