Facets, first draft
This commit is contained in:
parent
6224a4dbf3
commit
ff1feff82b
184
src/actor.ts
184
src/actor.ts
|
@ -21,7 +21,7 @@ export interface Entity {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Ref {
|
export interface Ref {
|
||||||
readonly relay: Actor;
|
readonly relay: Facet;
|
||||||
readonly target: Partial<Entity>;
|
readonly target: Partial<Entity>;
|
||||||
readonly attenuation?: Attenuation;
|
readonly attenuation?: Attenuation;
|
||||||
}
|
}
|
||||||
|
@ -29,24 +29,28 @@ export interface Ref {
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
export function isRef(v: any): v is Ref {
|
export function isRef(v: any): v is Ref {
|
||||||
return 'relay' in v && v.relay instanceof Actor && 'target' in v;
|
return 'relay' in v && v.relay instanceof Facet && 'target' in v;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toRef(_v: any): Ref | undefined {
|
export function toRef(_v: any): Ref | undefined {
|
||||||
return isRef(_v) ? _v : void 0;
|
return isRef(_v) ? _v : void 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OutboundAssertion = { handle: Handle, peer: Ref, established: boolean };
|
||||||
|
type OutboundMap = Map<Handle, OutboundAssertion>;
|
||||||
|
|
||||||
let nextActorId = 0;
|
let nextActorId = 0;
|
||||||
export const __setNextActorId = (v: number) => nextActorId = v;
|
export const __setNextActorId = (v: number) => nextActorId = v;
|
||||||
|
|
||||||
export class Actor {
|
export class Actor {
|
||||||
readonly id = nextActorId++;
|
readonly id = nextActorId++;
|
||||||
readonly outbound: Map<Handle, Ref>;
|
readonly root: Facet;
|
||||||
exitReason: ExitReason = null;
|
exitReason: ExitReason = null;
|
||||||
readonly exitHooks: Array<LocalAction> = [];
|
readonly exitHooks: Array<LocalAction> = [];
|
||||||
|
|
||||||
constructor(initialAssertions = new Map<Handle, Ref>()) {
|
constructor(bootProc: LocalAction, initialAssertions: OutboundMap = new Map()) {
|
||||||
this.outbound = initialAssertions;
|
this.root = new Facet(this, null, initialAssertions);
|
||||||
|
Turn.for(new Facet(this, this.root), stopIfInertAfter(bootProc));
|
||||||
}
|
}
|
||||||
|
|
||||||
atExit(a: LocalAction): void {
|
atExit(a: LocalAction): void {
|
||||||
|
@ -60,63 +64,145 @@ export class Actor {
|
||||||
console.error(`Actor ${this.id} crashed:`, this.exitReason.err);
|
console.error(`Actor ${this.id} crashed:`, this.exitReason.err);
|
||||||
}
|
}
|
||||||
this.exitHooks.forEach(hook => hook(t));
|
this.exitHooks.forEach(hook => hook(t));
|
||||||
queueTask(() => Turn.for(
|
queueTask(() => Turn.for(this.root, t => this.root._terminate(t, false), true));
|
||||||
t.actor,
|
|
||||||
t => this.outbound.forEach((peer, h) => t._retract(peer, h)),
|
|
||||||
true));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let nextHandle = 0;
|
export class Facet {
|
||||||
|
readonly id = nextActorId++;
|
||||||
|
readonly actor: Actor;
|
||||||
|
readonly parent: Facet | null;
|
||||||
|
readonly children = new Set<Facet>();
|
||||||
|
readonly outbound: OutboundMap;
|
||||||
|
readonly shutdownActions: Array<LocalAction> = [];
|
||||||
|
// ^ shutdownActions are not exitHooks - those run even on error. These are for clean shutdown
|
||||||
|
isLive = true;
|
||||||
|
inertCheckPreventers = 0;
|
||||||
|
|
||||||
|
constructor(actor: Actor, parent: Facet | null, initialAssertions: OutboundMap = new Map()) {
|
||||||
|
this.actor = actor;
|
||||||
|
this.parent = parent;
|
||||||
|
if (parent) parent.children.add(this);
|
||||||
|
this.outbound = initialAssertions;
|
||||||
|
}
|
||||||
|
|
||||||
|
onStop(a: LocalAction): void {
|
||||||
|
this.shutdownActions.push(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
isInert(): boolean {
|
||||||
|
return this.children.size === 0 && this.outbound.size === 0 && this.inertCheckPreventers === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
preventInertCheck(): () => void {
|
||||||
|
let armed = true;
|
||||||
|
this.inertCheckPreventers++;
|
||||||
|
return () => {
|
||||||
|
if (!armed) return;
|
||||||
|
armed = false;
|
||||||
|
this.inertCheckPreventers--;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_terminate(t: Turn, orderly: boolean): void {
|
||||||
|
if (!this.isLive) return;
|
||||||
|
this.isLive = false;
|
||||||
|
|
||||||
|
const parent = this.parent;
|
||||||
|
if (parent) parent.children.delete(this);
|
||||||
|
|
||||||
|
t._inFacet(this, t => {
|
||||||
|
this.children.forEach(child => child._terminate(t, orderly));
|
||||||
|
if (orderly) this.shutdownActions.forEach(a => a(t));
|
||||||
|
this.outbound.forEach(e => t._retract(e));
|
||||||
|
|
||||||
|
if (orderly) {
|
||||||
|
queueTask(() => {
|
||||||
|
if (parent) {
|
||||||
|
if (parent.isInert()) {
|
||||||
|
Turn.for(parent, t => parent._terminate(t, true));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Turn.for(this.actor.root, t => this.actor.terminateWith(t, { ok: true }), true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function _sync_impl(turn: Turn, e: Partial<Entity>, peer: Ref): void {
|
export function _sync_impl(turn: Turn, e: Partial<Entity>, peer: Ref): void {
|
||||||
e.sync ? e.sync!(turn, peer) : turn.message(peer, true);
|
e.sync ? e.sync!(turn, peer) : turn.message(peer, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let nextHandle = 0;
|
||||||
let nextTurnId = 0;
|
let nextTurnId = 0;
|
||||||
|
|
||||||
export class Turn {
|
export class Turn {
|
||||||
readonly id = nextTurnId++;
|
readonly id = nextTurnId++;
|
||||||
readonly actor: Actor;
|
readonly activeFacet: Facet;
|
||||||
queues: Map<Actor, LocalAction[]> | null = new Map();
|
queues: Map<Facet, LocalAction[]> | null;
|
||||||
|
|
||||||
static for(actor: Actor, f: LocalAction, zombieTurn = false): void {
|
static for(facet: Facet, f: LocalAction, zombieTurn = false): void {
|
||||||
if ((actor.exitReason === null) === zombieTurn) return;
|
if (!zombieTurn) {
|
||||||
const t = new Turn(actor);
|
if (facet.actor.exitReason !== null) return;
|
||||||
|
if (!facet.isLive) return;
|
||||||
|
}
|
||||||
|
const t = new Turn(facet);
|
||||||
try {
|
try {
|
||||||
f(t);
|
f(t);
|
||||||
t.queues!.forEach((q, a) => queueTask(() => q.forEach(f => Turn.for(a, f))));
|
t.queues!.forEach((q, facet) => queueTask(() => q.forEach(f => Turn.for(facet, f))));
|
||||||
t.queues = null;
|
t.queues = null;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Turn.for(actor, t => actor.terminateWith(t, { ok: false, err }));
|
Turn.for(facet.actor.root, t => facet.actor.terminateWith(t, { ok: false, err }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private constructor(actor: Actor) {
|
private constructor(facet: Facet, queues = new Map<Facet, LocalAction[]>()) {
|
||||||
this.actor = actor;
|
this.activeFacet = facet;
|
||||||
|
this.queues = queues;
|
||||||
|
}
|
||||||
|
|
||||||
|
_inFacet(facet: Facet, f: LocalAction): void {
|
||||||
|
const t = new Turn(facet, this.queues!);
|
||||||
|
f(t);
|
||||||
|
t.queues = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref<T extends Partial<Entity>>(e: T): Ref {
|
ref<T extends Partial<Entity>>(e: T): Ref {
|
||||||
return { relay: this.actor, target: e };
|
return { relay: this.activeFacet, target: e };
|
||||||
}
|
}
|
||||||
|
|
||||||
spawn(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): void {
|
facet(bootProc: LocalAction): Facet {
|
||||||
this.enqueue(this.actor, () => {
|
const newFacet = new Facet(this.activeFacet.actor, this.activeFacet);
|
||||||
const newOutbound = new Map<Handle, Ref>();
|
this._inFacet(newFacet, stopIfInertAfter(bootProc));
|
||||||
initialAssertions.forEach(key => {
|
return newFacet;
|
||||||
newOutbound.set(key, this.actor.outbound.get(key)!); // we trust initialAssertions
|
}
|
||||||
this.actor.outbound.delete(key);
|
|
||||||
});
|
stop(facet: Facet = this.activeFacet, continuation?: LocalAction) {
|
||||||
queueTask(() => Turn.for(new Actor(newOutbound), bootProc));
|
this.enqueue(facet.parent!, t => {
|
||||||
|
facet._terminate(t, true);
|
||||||
|
if (continuation) continuation(t);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
quit(): void {
|
spawn(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): void {
|
||||||
this.enqueue(this.actor, t => this.actor.terminateWith(t, { ok: true }));
|
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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stopActor(): void {
|
||||||
|
this.enqueue(this.activeFacet.actor.root, t => this.activeFacet.actor.terminateWith(t, { ok: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
crash(err: Error): void {
|
crash(err: Error): void {
|
||||||
this.enqueue(this.actor, t => this.actor.terminateWith(t, { ok: false, err }));
|
this.enqueue(this.activeFacet.actor.root, t => this.activeFacet.actor.terminateWith(t, { ok: false, err }));
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(ref: Ref, assertion: Assertion): Handle {
|
assert(ref: Ref, assertion: Assertion): Handle {
|
||||||
|
@ -128,8 +214,10 @@ export class Turn {
|
||||||
_assert(ref: Ref, assertion: Assertion, h: Handle) {
|
_assert(ref: Ref, assertion: Assertion, h: Handle) {
|
||||||
const a = runRewrites(ref.attenuation, assertion);
|
const a = runRewrites(ref.attenuation, assertion);
|
||||||
if (a !== null) {
|
if (a !== null) {
|
||||||
|
const e = { handle: h, peer: ref, established: false };
|
||||||
|
this.activeFacet.outbound.set(h, e);
|
||||||
this.enqueue(ref.relay, t => {
|
this.enqueue(ref.relay, t => {
|
||||||
this.actor.outbound.set(h, ref);
|
e.established = true;
|
||||||
ref.target.assert?.(t, a, h);
|
ref.target.assert?.(t, a, h);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -137,9 +225,9 @@ export class Turn {
|
||||||
|
|
||||||
retract(h: Handle | undefined): void {
|
retract(h: Handle | undefined): void {
|
||||||
if (h !== void 0) {
|
if (h !== void 0) {
|
||||||
const peer = this.actor.outbound.get(h);
|
const e = this.activeFacet.outbound.get(h);
|
||||||
if (peer === void 0) return;
|
if (e === void 0) return;
|
||||||
this._retract(peer, h);
|
this._retract(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,10 +237,13 @@ export class Turn {
|
||||||
return newHandle;
|
return newHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
_retract(ref: Ref, handle: Handle): void {
|
_retract(e: OutboundAssertion): void {
|
||||||
this.enqueue(ref.relay, t => {
|
this.activeFacet.outbound.delete(e.handle);
|
||||||
this.actor.outbound.delete(handle);
|
this.enqueue(e.peer.relay, t => {
|
||||||
ref.target.retract?.(t, handle);
|
if (e.established) {
|
||||||
|
e.established = false;
|
||||||
|
e.peer.target.retract?.(t, e.handle);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +260,7 @@ 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: Actor, a: LocalAction): void {
|
enqueue(relay: Facet, a: 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");
|
||||||
}
|
}
|
||||||
|
@ -180,6 +271,17 @@ export class Turn {
|
||||||
if (this.queues !== null) {
|
if (this.queues !== null) {
|
||||||
throw new Error("Attempt to freshen a non-stale Turn");
|
throw new Error("Attempt to freshen a non-stale Turn");
|
||||||
}
|
}
|
||||||
Turn.for(this.actor, a);
|
Turn.for(this.activeFacet, a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stopIfInertAfter(a: LocalAction): LocalAction {
|
||||||
|
return t => {
|
||||||
|
a(t);
|
||||||
|
t.enqueue(t.activeFacet, t => {
|
||||||
|
if ((t.activeFacet.parent && !t.activeFacet.parent.isLive) || t.activeFacet.isInert()) {
|
||||||
|
t.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -26,9 +26,9 @@ export default function (t: Turn, arg: Assertion) {
|
||||||
const count = newValue - prevValue;
|
const count = newValue - prevValue;
|
||||||
prevValue = newValue;
|
prevValue = newValue;
|
||||||
startTime = endTime;
|
startTime = endTime;
|
||||||
console.log(`Box ${t.actor.id}: got ${newValue} (${count / delta} Hz)`);
|
console.log(`Box ${t.activeFacet.id}: got ${newValue} (${count / delta} Hz)`);
|
||||||
}
|
}
|
||||||
if (newValue === LIMIT) t.quit();
|
if (newValue === LIMIT) t.stopActor();
|
||||||
setValue(t, newValue);
|
setValue(t, newValue);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -23,8 +23,8 @@ export default function (t: Turn, ds: Ref) {
|
||||||
},
|
},
|
||||||
retract(t: Turn) {
|
retract(t: Turn) {
|
||||||
if (--count === 0) {
|
if (--count === 0) {
|
||||||
console.log(`Client ${t.actor.id}: detected box termination`);
|
console.log(`Client ${t.activeFacet.id}: detected box termination`);
|
||||||
t.quit();
|
t.stopActor();
|
||||||
}
|
}
|
||||||
// console.log('dec to', count);
|
// console.log('dec to', count);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Assertion, Entity, Handle, LocalAction, Ref, Turn } from 'actor';
|
import { Assertion, Entity, Handle, LocalAction, Ref, Turn } from 'actor';
|
||||||
import { Dictionary, IdentityMap, is, Record, Tuple } from '@preserves/core';
|
import { Dictionary, IdentityMap, is, preserves, Record, Tuple } from '@preserves/core';
|
||||||
import { Bag, ChangeDescription } from './bag';
|
import { Bag, ChangeDescription } from './bag';
|
||||||
|
|
||||||
import { fromObserve, toObserve, Observe } from './gen/dataspace';
|
import { fromObserve, toObserve, Observe } from './gen/dataspace';
|
||||||
|
@ -41,7 +41,7 @@ export class Dataspace implements Partial<Entity> {
|
||||||
readonly subscriptions = new Dictionary<Ref, Map<Ref, Dictionary<Ref, Handle>>>();
|
readonly subscriptions = new Dictionary<Ref, Map<Ref, Dictionary<Ref, Handle>>>();
|
||||||
|
|
||||||
assert(turn: Turn, rec: Assertion, handle: Handle): void {
|
assert(turn: Turn, rec: Assertion, handle: Handle): void {
|
||||||
// console.log(preserves`ds ${turn.actor.id} assert ${rec} ${handle}`);
|
// console.log(preserves`ds ${turn.activeFacet.id} assert ${rec} ${handle}`);
|
||||||
if (!Record.isRecord<Assertion, Tuple<Assertion>, Ref>(rec)) return;
|
if (!Record.isRecord<Assertion, Tuple<Assertion>, Ref>(rec)) return;
|
||||||
this.handleMap.set(handle, rec);
|
this.handleMap.set(handle, rec);
|
||||||
if (this.assertions.change(rec, +1) !== ChangeDescription.ABSENT_TO_PRESENT) return;
|
if (this.assertions.change(rec, +1) !== ChangeDescription.ABSENT_TO_PRESENT) return;
|
||||||
|
@ -62,7 +62,7 @@ export class Dataspace implements Partial<Entity> {
|
||||||
|
|
||||||
retract(turn: Turn, upstreamHandle: Handle): void {
|
retract(turn: Turn, upstreamHandle: Handle): void {
|
||||||
const rec = this.handleMap.get(upstreamHandle);
|
const rec = this.handleMap.get(upstreamHandle);
|
||||||
// console.log(preserves`ds ${turn.actor.id} retract ${rec} ${upstreamHandle}`);
|
// console.log(preserves`ds ${turn.activeFacet.id} retract ${rec} ${upstreamHandle}`);
|
||||||
if (rec === void 0) return;
|
if (rec === void 0) return;
|
||||||
this.handleMap.delete(upstreamHandle);
|
this.handleMap.delete(upstreamHandle);
|
||||||
if (this.assertions.change(rec, -1) !== ChangeDescription.PRESENT_TO_ABSENT) return;
|
if (this.assertions.change(rec, -1) !== ChangeDescription.PRESENT_TO_ABSENT) return;
|
||||||
|
@ -81,7 +81,7 @@ export class Dataspace implements Partial<Entity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
message(turn: Turn, rec: Assertion): void {
|
message(turn: Turn, rec: Assertion): void {
|
||||||
// console.log(preserves`ds ${turn.actor.id} message ${rec}`);
|
// console.log(preserves`ds ${turn.activeFacet.id} message ${rec}`);
|
||||||
if (!Record.isRecord<Assertion, Tuple<Assertion>, Ref>(rec)) return;
|
if (!Record.isRecord<Assertion, Tuple<Assertion>, Ref>(rec)) return;
|
||||||
this.subscriptions.get(rec.label)?.forEach((_seen, peer) => turn.message(peer, rec));
|
this.subscriptions.get(rec.label)?.forEach((_seen, peer) => turn.message(peer, rec));
|
||||||
}
|
}
|
||||||
|
|
16
src/main.ts
16
src/main.ts
|
@ -14,23 +14,25 @@ const Instance = Record.makeConstructor<{moduleName: string, arg: Assertion}>()(
|
||||||
|
|
||||||
function spawnWorker(t: Turn, moduleName: string, arg: Assertion) {
|
function spawnWorker(t: Turn, moduleName: string, arg: Assertion) {
|
||||||
const w = new Worker(path.join(__dirname, 'wload.js'));
|
const w = new Worker(path.join(__dirname, 'wload.js'));
|
||||||
|
const reenable = t.activeFacet.preventInertCheck();
|
||||||
spawnRelay(t, {
|
spawnRelay(t, {
|
||||||
packetWriter: bs => w.postMessage(bs),
|
packetWriter: bs => w.postMessage(bs),
|
||||||
setup(t: Turn, r: Relay) {
|
setup(t: Turn, r: Relay) {
|
||||||
w.on('message', bs => r.accept(bs));
|
w.on('message', bs => r.accept(bs));
|
||||||
w.on('error', err => Turn.for(t.actor, t => t.crash(err)));
|
w.on('error', err => Turn.for(t.activeFacet, t => t.crash(err)));
|
||||||
w.on('exit', code => Turn.for(t.actor, t => {
|
w.on('exit', code => Turn.for(t.activeFacet, t => {
|
||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
t.quit();
|
t.stopActor();
|
||||||
} else {
|
} else {
|
||||||
t.crash(new Error(`Worker crashed with code ${code}`));
|
t.crash(new Error(`Worker crashed with code ${code}`));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
initialOid: 0,
|
initialOid: 0,
|
||||||
}).then(factory => Turn.for(new Actor(), t => {
|
}).then(factory => {
|
||||||
t.assert(factory!, Instance(moduleName, arg));
|
reenable();
|
||||||
}));
|
new Actor(t => t.assert(factory, Instance(moduleName, arg)));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function spawnModule(t: Turn, moduleName: string, arg: Assertion) {
|
function spawnModule(t: Turn, moduleName: string, arg: Assertion) {
|
||||||
|
@ -39,7 +41,7 @@ function spawnModule(t: Turn, moduleName: string, arg: Assertion) {
|
||||||
|
|
||||||
// __setNextActorId(1000);
|
// __setNextActorId(1000);
|
||||||
|
|
||||||
Turn.for(new Actor(), async (t: Turn) => {
|
new Actor(async (t: Turn) => {
|
||||||
const ds_unproxied = t.ref(new Dataspace());
|
const ds_unproxied = t.ref(new Dataspace());
|
||||||
|
|
||||||
const ds = ds_unproxied;
|
const ds = ds_unproxied;
|
||||||
|
|
19
src/relay.ts
19
src/relay.ts
|
@ -1,4 +1,4 @@
|
||||||
import { Actor, Assertion, Entity, Handle, Ref, Turn } from './actor.js';
|
import { Actor, Assertion, Entity, Facet, Handle, Ref, Turn } from './actor.js';
|
||||||
import { BytesLike, canonicalString, Decoder, encode, FlexMap, IdentityMap, mapPointers, underlying, Value } from '@preserves/core';
|
import { BytesLike, canonicalString, Decoder, encode, FlexMap, IdentityMap, mapPointers, underlying, Value } from '@preserves/core';
|
||||||
import * as IO from './gen/protocol.js';
|
import * as IO from './gen/protocol.js';
|
||||||
import { myRef, toWireRef, WireRef, WireSymbol, yourRef } from './protocol.js';
|
import { myRef, toWireRef, WireRef, WireSymbol, yourRef } from './protocol.js';
|
||||||
|
@ -106,9 +106,8 @@ export class Membrane {
|
||||||
|
|
||||||
export const INERT_REF: Ref = {
|
export const INERT_REF: Ref = {
|
||||||
relay: (() => {
|
relay: (() => {
|
||||||
const a = new Actor();
|
const a = new Actor(t => t.stop());
|
||||||
a.exitReason = { ok: true };
|
return a.root;
|
||||||
return a;
|
|
||||||
})(),
|
})(),
|
||||||
target: {},
|
target: {},
|
||||||
};
|
};
|
||||||
|
@ -123,7 +122,7 @@ export interface RelayOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Relay {
|
export class Relay {
|
||||||
readonly actor: Actor;
|
readonly facet: Facet;
|
||||||
readonly w: PacketWriter;
|
readonly w: PacketWriter;
|
||||||
readonly inboundAssertions = new IdentityMap<Handle, {
|
readonly inboundAssertions = new IdentityMap<Handle, {
|
||||||
localHandle: Handle,
|
localHandle: Handle,
|
||||||
|
@ -141,10 +140,12 @@ export class Relay {
|
||||||
.replacePointerDecoder<WireRef>(d => toWireRef(d.next()));
|
.replacePointerDecoder<WireRef>(d => toWireRef(d.next()));
|
||||||
|
|
||||||
constructor(t: Turn, options: RelayOptions) {
|
constructor(t: Turn, options: RelayOptions) {
|
||||||
this.actor = t.actor;
|
this.facet = t.activeFacet;
|
||||||
this.w = options.packetWriter;
|
this.w = options.packetWriter;
|
||||||
this.debug = options.debug ?? false;
|
this.debug = options.debug ?? false;
|
||||||
this.trustPeer = options.trustPeer ?? true;
|
this.trustPeer = options.trustPeer ?? true;
|
||||||
|
|
||||||
|
this.facet.preventInertCheck();
|
||||||
options.setup(t, this);
|
options.setup(t, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,7 +230,7 @@ export class Relay {
|
||||||
send(remoteOid: IO.Oid, m: IO.Event): void {
|
send(remoteOid: IO.Oid, m: IO.Event): void {
|
||||||
if (this.pendingTurn.length === 0) {
|
if (this.pendingTurn.length === 0) {
|
||||||
queueTask(() => {
|
queueTask(() => {
|
||||||
if (this.debug) console.log('OUT', this.pendingTurn.asPreservesText());
|
if (this.debug) console.log('OUT', IO.fromTurn(this.pendingTurn).asPreservesText());
|
||||||
this.w(underlying(encode<WireRef>(IO.fromTurn(this.pendingTurn), {
|
this.w(underlying(encode<WireRef>(IO.fromTurn(this.pendingTurn), {
|
||||||
canonical: true,
|
canonical: true,
|
||||||
encodePointer: (e, n) => {
|
encodePointer: (e, n) => {
|
||||||
|
@ -251,14 +252,14 @@ export class Relay {
|
||||||
}
|
}
|
||||||
|
|
||||||
accept(bs: BytesLike): void {
|
accept(bs: BytesLike): void {
|
||||||
Turn.for(this.actor, t => {
|
Turn.for(this.facet, t => {
|
||||||
this.decoder.write(bs);
|
this.decoder.write(bs);
|
||||||
while (true) {
|
while (true) {
|
||||||
const rawTurn = this.decoder.try_next();
|
const rawTurn = this.decoder.try_next();
|
||||||
if (rawTurn === void 0) break;
|
if (rawTurn === void 0) break;
|
||||||
const wireTurn = IO.toTurn(rawTurn);
|
const wireTurn = IO.toTurn(rawTurn);
|
||||||
if (wireTurn === void 0) throw new Error("Bad IO.Turn");
|
if (wireTurn === void 0) throw new Error("Bad IO.Turn");
|
||||||
if (this.debug) console.log('IN', wireTurn.asPreservesText());
|
if (this.debug) console.log('IN', rawTurn.asPreservesText());
|
||||||
wireTurn.forEach(v => {
|
wireTurn.forEach(v => {
|
||||||
const { oid: localOid, event: m } = v;
|
const { oid: localOid, event: m } = v;
|
||||||
this.handle(t, this.lookupLocal(localOid), m);
|
this.handle(t, this.lookupLocal(localOid), m);
|
||||||
|
|
|
@ -10,24 +10,30 @@ const [ moduleName, hexCap ] = process.argv.slice(2);
|
||||||
const cap = sturdyDecode(Bytes.fromHex(hexCap ?? ''));
|
const cap = sturdyDecode(Bytes.fromHex(hexCap ?? ''));
|
||||||
|
|
||||||
const socket = net.createConnection({ port: 5999, host: 'localhost' }, () => {
|
const socket = net.createConnection({ port: 5999, host: 'localhost' }, () => {
|
||||||
Turn.for(new Actor(), t => {
|
new Actor(t => {
|
||||||
let shutdownRef: Ref;
|
let shutdownRef: Ref;
|
||||||
|
const reenable = t.activeFacet.preventInertCheck();
|
||||||
|
const connectionClosedRef = t.ref({
|
||||||
|
retract(t) { t.stopActor(); },
|
||||||
|
});
|
||||||
spawnRelay(t, {
|
spawnRelay(t, {
|
||||||
packetWriter: bs => socket.write(bs),
|
packetWriter: bs => socket.write(bs),
|
||||||
setup(t: Turn, r: Relay) {
|
setup(t: Turn, r: Relay) {
|
||||||
socket.on('error', err => t.freshen(t =>
|
socket.on('error', err => t.freshen(t =>
|
||||||
((err as any).code === 'ECONNRESET') ? t.quit() : t.crash(err)));
|
((err as any).code === 'ECONNRESET') ? t.stopActor() : t.crash(err)));
|
||||||
socket.on('close', () => t.freshen(t => t.quit()));
|
socket.on('close', () => t.freshen(t => t.stopActor()));
|
||||||
socket.on('end', () => t.freshen(t => t.quit()));
|
socket.on('end', () => t.freshen(t => t.stopActor()));
|
||||||
socket.on('data', data => r.accept(data));
|
socket.on('data', data => r.accept(data));
|
||||||
t.actor.atExit(() => socket.destroy());
|
t.activeFacet.actor.atExit(() => socket.destroy());
|
||||||
|
t.assert(connectionClosedRef, true);
|
||||||
shutdownRef = t.ref({
|
shutdownRef = t.ref({
|
||||||
retract(t) { t.quit(); }
|
retract(t) { t.stopActor(); }
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
initialOid: 0,
|
initialOid: 0,
|
||||||
// debug: true,
|
// debug: true,
|
||||||
}).then(gatekeeper => import(moduleName).then(m => t.freshen(t => {
|
}).then(gatekeeper => import(moduleName).then(m => t.freshen(t => {
|
||||||
|
reenable();
|
||||||
t.assert(shutdownRef, true);
|
t.assert(shutdownRef, true);
|
||||||
t.assert(gatekeeper, fromResolve(Resolve({
|
t.assert(gatekeeper, fromResolve(Resolve({
|
||||||
sturdyref: asSturdyRef(cap),
|
sturdyref: asSturdyRef(cap),
|
||||||
|
|
|
@ -62,5 +62,5 @@ function runSession(t: Turn, uid: UserId, session: Ref) {
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
rl.on('close', () => t.freshen(t => t.quit()));
|
rl.on('close', () => t.freshen(t => t.stopActor()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,8 @@ mint('syndicate', secretKey).then(v => {
|
||||||
console.log(sturdyEncode(fromSturdyRef(v)).toHex());
|
console.log(sturdyEncode(fromSturdyRef(v)).toHex());
|
||||||
});
|
});
|
||||||
|
|
||||||
Turn.for(new Actor(), t => {
|
new Actor(t => {
|
||||||
|
t.activeFacet.preventInertCheck();
|
||||||
const ds = t.ref(new Dataspace());
|
const ds = t.ref(new Dataspace());
|
||||||
|
|
||||||
function spawnConnection(t: Turn, socket: net.Socket) {
|
function spawnConnection(t: Turn, socket: net.Socket) {
|
||||||
|
@ -24,11 +25,11 @@ Turn.for(new Actor(), t => {
|
||||||
packetWriter: bs => socket.write(bs),
|
packetWriter: bs => socket.write(bs),
|
||||||
setup(t: Turn, r: Relay) {
|
setup(t: Turn, r: Relay) {
|
||||||
socket.on('error', err => t.freshen(t =>
|
socket.on('error', err => t.freshen(t =>
|
||||||
((err as any).code === 'ECONNRESET') ? t.quit() : t.crash(err)));
|
((err as any).code === 'ECONNRESET') ? t.stopActor() : t.crash(err)));
|
||||||
socket.on('close', () => t.freshen(t => t.quit()));
|
socket.on('close', () => t.freshen(t => t.stopActor()));
|
||||||
socket.on('end', () => t.freshen(t => t.quit()));
|
socket.on('end', () => t.freshen(t => t.stopActor()));
|
||||||
socket.on('data', data => r.accept(data));
|
socket.on('data', data => r.accept(data));
|
||||||
t.actor.atExit(() => socket.destroy());
|
t.activeFacet.actor.atExit(() => socket.destroy());
|
||||||
},
|
},
|
||||||
initialRef: t.ref({
|
initialRef: t.ref({
|
||||||
handleMap: new IdentityMap<Handle, Handle>(),
|
handleMap: new IdentityMap<Handle, Handle>(),
|
||||||
|
@ -60,7 +61,8 @@ Turn.for(new Actor(), t => {
|
||||||
const server = net.createServer(socket => t.freshen(t => spawnConnection(t, socket)));
|
const server = net.createServer(socket => t.freshen(t => spawnConnection(t, socket)));
|
||||||
server.on('error', err => t.freshen(t => t.crash(err)));
|
server.on('error', err => t.freshen(t => t.crash(err)));
|
||||||
server.listen(5999, '0.0.0.0', 512);
|
server.listen(5999, '0.0.0.0', 512);
|
||||||
t.actor.atExit(() => {
|
t.activeFacet.preventInertCheck();
|
||||||
|
t.activeFacet.actor.atExit(() => {
|
||||||
try {
|
try {
|
||||||
server.close();
|
server.close();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -27,6 +27,7 @@ export default function (t: Turn, ds: Ref) {
|
||||||
});
|
});
|
||||||
|
|
||||||
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||||
|
t.activeFacet.actor.atExit(_t => rl.close());
|
||||||
|
|
||||||
rl.on('line', (line: string) => t.freshen(t => {
|
rl.on('line', (line: string) => t.freshen(t => {
|
||||||
if (line.toLowerCase().startsWith('/nick ')) {
|
if (line.toLowerCase().startsWith('/nick ')) {
|
||||||
|
@ -36,5 +37,5 @@ export default function (t: Turn, ds: Ref) {
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
rl.on('close', () => t.freshen(t => t.quit()));
|
rl.on('close', () => t.freshen(t => t.stopActor()));
|
||||||
}
|
}
|
||||||
|
|
11
src/wload.ts
11
src/wload.ts
|
@ -18,15 +18,16 @@ type TaskState =
|
||||||
| { readonly state: "shutdown_pending" }
|
| { readonly state: "shutdown_pending" }
|
||||||
| { readonly state: "running", shutdownRef: Ref };
|
| { readonly state: "running", shutdownRef: Ref };
|
||||||
|
|
||||||
Turn.for(new Actor(), t => {
|
new Actor(t => {
|
||||||
const p = parentPort!;
|
const p = parentPort!;
|
||||||
let taskState: TaskState = { state: "start_pending" };
|
let taskState: TaskState = { state: "start_pending" };
|
||||||
|
t.activeFacet.preventInertCheck();
|
||||||
spawnRelay(t, {
|
spawnRelay(t, {
|
||||||
nextLocalOid: STARTING_ACTOR_ID + 500000000,
|
nextLocalOid: STARTING_ACTOR_ID + 500000000,
|
||||||
packetWriter: bs => p.postMessage(bs),
|
packetWriter: bs => p.postMessage(bs),
|
||||||
setup(t: Turn, r: Relay) {
|
setup(t: Turn, r: Relay) {
|
||||||
p.on('message', bs => r.accept(bs));
|
p.on('message', bs => r.accept(bs));
|
||||||
p.on('close', () => Turn.for(t.actor, t => t.quit()));
|
p.on('close', () => Turn.for(t.activeFacet, t => t.stopActor()));
|
||||||
},
|
},
|
||||||
initialRef: t.ref({
|
initialRef: t.ref({
|
||||||
async assert(t, inst) {
|
async assert(t, inst) {
|
||||||
|
@ -37,16 +38,16 @@ Turn.for(new Actor(), t => {
|
||||||
|
|
||||||
const m = await import(Instance._.moduleName(inst));
|
const m = await import(Instance._.moduleName(inst));
|
||||||
t.freshen(t => t.spawn(t => {
|
t.freshen(t => t.spawn(t => {
|
||||||
t.actor.atExit(() => {
|
t.activeFacet.actor.atExit(() => {
|
||||||
console.log('Worker terminating');
|
console.log('Worker terminating');
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
if (taskState.state === "shutdown_pending") {
|
if (taskState.state === "shutdown_pending") {
|
||||||
t.quit();
|
t.stopActor();
|
||||||
} else {
|
} else {
|
||||||
taskState = {
|
taskState = {
|
||||||
state: "running",
|
state: "running",
|
||||||
shutdownRef: t.ref({ message(t) { t.quit(); } }),
|
shutdownRef: t.ref({ message(t) { t.stopActor(); } }),
|
||||||
};
|
};
|
||||||
m.default(t, Instance._.arg(inst));
|
m.default(t, Instance._.arg(inst));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue