Skip to content

Commit

Permalink
wip(gems): start gem creation powers
Browse files Browse the repository at this point in the history
  • Loading branch information
kumavis committed Sep 13, 2024
1 parent 7da995c commit 9b7fc40
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 140 deletions.
159 changes: 31 additions & 128 deletions packages/gems/src/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
/* global setTimeout */

import { makeCapTP } from '@endo/captp';
import { Far, makeCapTP } from '@endo/captp';
import { makeDurableZone } from '@agoric/zone/durable.js';

/** @import { Stream } from '@endo/stream' */

const noop = (...args) => {};
const never = new Promise(() => {});
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

export const util = { noop, never, delay };

const getRandomId = () => Math.random().toString(36).slice(2);

const initEmpty = () => {};
Expand Down Expand Up @@ -54,114 +48,6 @@ export const makeMessageCapTP = (
};
};

// const makePersistenceNode = () => {
// let value;
// return {
// get() {
// return value;
// },
// set(newValue) {
// if (typeof newValue !== 'string') {
// throw new Error(
// `persistence node expected string (got "${typeof newValue}")`,
// );
// }
// value = newValue;
// },
// };
// };

// const makeWakeController = ({ name, makeFacet }) => {
// let isAwake = false;
// let target;
// let currentFacetId;

// let controller;

// const triggerSleep = targetFacetId => {
// if (currentFacetId === targetFacetId) {
// console.log(
// `gem:${name}/facet:${targetFacetId} being put to sleep (due to GC)`,
// );
// controller.sleep();
// }
// };

// // is it theoretically possible for a facet to get GC'd while handling a message?
// // i was unable to observe it happening
// const registry = new FinalizationRegistry(facetId => {
// console.log(`gem:${name}/facet:${facetId} has been garbage collected.`);
// triggerSleep(facetId);
// });

// controller = {
// async wake() {
// await null;
// // need to handle case where marked as awake but target is garbage collected
// if (isAwake && target && target.deref() !== undefined)
// return target.deref();
// // bug when wake is inflight
// const facetId = Math.random().toString(36).slice(2);
// // simulate startup process
// await delay(200);
// const facet = await makeFacet({
// // for debugging:
// facetId,
// });
// target = new WeakRef(facet);
// currentFacetId = facetId;
// registry.register(facet, facetId);
// console.log(`gem:${name}/facet:${facetId} created`);
// isAwake = true;
// return facet;
// },
// async sleep() {
// await null;
// if (!isAwake) return;
// console.log(`gem:${name}/facet:${currentFacetId} being put to sleep`);
// // simulate shutdown process
// // bug when sleep is inflight
// await delay(200);
// target = undefined;
// isAwake = false;
// },
// isAwake() {
// return isAwake;
// },
// assertAwake() {
// if (!isAwake) {
// throw new Error('not awake');
// }
// },
// };

// return controller;
// };

// const makeWrapper = (name, wakeController, methodNames) => {
// return Object.fromEntries(
// methodNames.map(methodName => {
// return [
// methodName,
// async function wrapperFn(...args) {
// console.log(`gem:${name} ${methodName} called`);
// const facet = await wakeController.wake();
// // load bearing codesmell against unseen enemies.
// // uhhhh, use (return-await + noop) to retain a strong reference to `facet` to
// // to prevent GC of facet before fully responding to a message.
// // we want the GC event to imply that the facet is no longer in use.
// // but we dont get many guarantees about when the GC event will fire.
// // this problem has not been witnessed,
// // this solution has not been verified.
// const result = await facet[methodName](...args);
// noop(facet);
// return result;
// },
// ];
// }),
// );
// };

// const makeGemFactory = ({ gemController }) => {
// const makeGem = ({ name, makeFacet, interface: iface }) => {
// const gemId = `gem:${getRandomId()}`;
Expand Down Expand Up @@ -211,34 +97,51 @@ export const makeMessageCapTP = (
// return makeGem;
// };

const makeGemFactory = (zone) => {
export const makeKernel = (baggage) => {
const zone = makeDurableZone(baggage);
const gemZone = zone.subZone('GemZone');
let kernel;
let gemCreationPowers;

const makeGem = ({ name, interfaceGuards, methods, init = initEmpty }) => {
// maybe instead want to expose the store making functions
const store = gemZone.mapStore(name);
if (!store.has('state')) {
store.init('state', harden(init()));
}
const finalizeStore = (instance) => {
const exposeEndowments = (instance) => {
// this late setting of the store seems to work around
// an issue where the store is missing despite being set in
// an issue where the store is missing when set in
// the initializer
instance.state.store = store;
instance.state.powers = harden({ gems: gemCreationPowers });

// instance.state.powers = Far('GemPowers', {
// incarnateEvalGem,
// };
};
// only good for singletons as here
const initWithStore = (store, ...args) => harden({ store, ...args });
const constructGem = zone.exoClass(name, interfaceGuards, initWithStore, methods, { finish: finalizeStore });
const initWithEndowments = (...args) => harden({ store: {}, powers: {}, ...args });
const constructGem = zone.exoClass(name, interfaceGuards, initWithEndowments, methods, { finish: exposeEndowments });
const gem = constructGem();
return gem;
}

return makeGem;
}

export const makeKernel = (baggage) => {
const zone = makeDurableZone(baggage);
const makeGem = makeGemFactory(zone);
return {
makeGem,
// kernel = zone.exo('kernel', undefined, { makeGem });
kernel = { makeGem };

const incarnateEvalGem = ({ name: childName, interface: childInterfaceGuards, code }) => {
// TODO: this could happen in another Realm
const compartment = new Compartment();
const methods = compartment.evaluate(code);
const gem = makeGem({
name: childName,
interfaceGuards: childInterfaceGuards,
methods: methods,
});
return gem;
};
gemCreationPowers = zone.exo('kernel:gemCreationPowers', undefined, { incarnateEvalGem });

return kernel;
};
20 changes: 10 additions & 10 deletions packages/gems/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ import test from '@endo/ses-ava/prepare-endo.js';
import '@agoric/swingset-liveslots/tools/setup-vat-data.js';
import { E } from '@endo/far';
import { M } from '@endo/patterns';
import { util } from '../src/index.js';
import { makeScenario } from './util.js';
import { makeKumavisStore } from '../src/kumavis-store.js';

const { delay } = util;

test('lifecycle - ping/gc', async t => {
const gemName = 'PingGem';
Expand Down Expand Up @@ -84,7 +80,8 @@ test('persistence - simple json counter', async t => {
t.deepEqual(await E(alice).getCount(), 3);
});

test.failing('kumavis store - serialization of gem refs', async t => {
// TODO: need to untangle captp remote refs for persistence
test.skip('kumavis store - serialization of gem refs', async t => {
const gemName = 'FriendGem';
const gemRecipe = {
name: gemName,
Expand All @@ -101,6 +98,7 @@ test.failing('kumavis store - serialization of gem refs', async t => {
return `added friend ${friend} (${friends.length} friends total)`;
},
async getFriends() {
const { store } = this.state;
const { friends } = store.get('state');
return friends;
},
Expand Down Expand Up @@ -133,19 +131,21 @@ test.skip('makeGem - widget factory', async t => {
init: () => ({ widgets: [] }),
methods: {
async makeWidget() {
const powers = this.state.powers;
const widget = powers.incarnateEvalGem({
const { gems } = this.state.powers;
const widget = gems.incarnateEvalGem({
name: 'widget',
interface: M.interface('Widget', {
sayHi: M.callWhen().returns(M.string()),
}),
code: 'async () => ({ sayHi: async () => "hi im a widget" })',
code: '({ sayHi: async () => "hi im a widget" })',
});
// you probably wouldnt want this to
// manage the retention of the widget,
// the consumer of the widget should do that.
retentionSet.add(widget.gemId);
return widget.exo;
const { store } = this.state;
const { widgets } = store.get('state');
store.set('state', { widgets: [...widgets, widget] });
return widget;
},
},
};
Expand Down
4 changes: 2 additions & 2 deletions packages/gems/test/util.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { makePipe } from '@endo/stream';
import { makeMessageCapTP, util, makeKernel } from '../src/index.js';
import { makeMessageCapTP, makeKernel } from '../src/index.js';
import { reincarnate } from '@agoric/swingset-liveslots/tools/setup-vat-data.js';

const { never } = util;
const never = new Promise(() => {});

const setupWorld = (fakeStore) => {
const { fakeVomKit } = reincarnate({ relaxDurabilityRules: false, fakeStore });
Expand Down

0 comments on commit 9b7fc40

Please sign in to comment.