@metamask/ocap-kernel
Version:
OCap kernel core components
256 lines • 9.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPromiseMethods = getPromiseMethods;
const errors_1 = require("@endo/errors");
const base_ts_1 = require("./base.cjs");
const queue_ts_1 = require("./queue.cjs");
const types_ts_1 = require("../../types.cjs");
const refcount_ts_1 = require("./refcount.cjs");
const kernel_slots_ts_1 = require("../utils/kernel-slots.cjs");
const parse_ref_ts_1 = require("../utils/parse-ref.cjs");
const promise_ref_ts_1 = require("../utils/promise-ref.cjs");
/**
* Create a promise store object that provides functionality for managing kernel promises.
*
* @param ctx - The store context.
* @returns A promise store object that maps various persistent kernel data
* structures onto `kv`.
*/
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function getPromiseMethods(ctx) {
const { incCounter, provideStoredQueue, getPrefixedKeys, refCountKey } = (0, base_ts_1.getBaseMethods)(ctx.kv);
const { enqueueRun } = (0, queue_ts_1.getQueueMethods)(ctx);
const { decrementRefCount } = (0, refcount_ts_1.getRefCountMethods)(ctx);
/**
* Create a new, unresolved kernel promise. The new promise will be born with
* a reference count of 1 on the assumption that the promise has just been
* imported from somewhere.
*
* @returns A tuple of the new promise's KRef and an object describing the
* new promise itself.
*/
function initKernelPromise() {
const kpr = {
state: 'unresolved',
subscribers: [],
};
const kpid = getNextPromiseId();
provideStoredQueue(kpid, false);
ctx.kv.set(`${kpid}.state`, 'unresolved');
ctx.kv.set(`${kpid}.subscribers`, '[]');
ctx.kv.set(refCountKey(kpid), '1');
// TODO(#562): Use logger instead.
// eslint-disable-next-line no-console
console.debug('initKernelPromise', kpid, ctx.kv.get(refCountKey(kpid)), kpr);
return [kpid, kpr];
}
/**
* Fetch the descriptive record for a kernel promise.
*
* @param kpid - The KRef of the kernel promise of interest.
* @returns An object describing the requested kernel promise.
*/
function getKernelPromise(kpid) {
const { context, isPromise } = (0, parse_ref_ts_1.parseRef)(kpid);
assert(context === 'kernel' && isPromise);
const state = ctx.kv.get(`${kpid}.state`);
if (state === undefined) {
throw Error(`unknown kernel promise ${kpid}`);
}
const result = { state };
switch (state) {
case 'unresolved': {
const decider = ctx.kv.get(`${kpid}.decider`);
if (decider !== '' && decider !== undefined) {
result.decider = decider;
}
const subscribers = ctx.kv.getRequired(`${kpid}.subscribers`);
result.subscribers = JSON.parse(subscribers);
break;
}
case 'fulfilled':
case 'rejected': {
result.value = JSON.parse(ctx.kv.getRequired(`${kpid}.value`));
break;
}
default:
throw Error(`unknown state for ${kpid}: ${state}`);
}
return result;
}
/**
* Expunge a kernel promise from the kernel's persistent state.
*
* @param kpid - The KRef of the kernel promise to delete.
*/
function deleteKernelPromise(kpid) {
ctx.kv.delete(`${kpid}.state`);
ctx.kv.delete(`${kpid}.decider`);
ctx.kv.delete(`${kpid}.subscribers`);
ctx.kv.delete(`${kpid}.value`);
ctx.kv.delete(refCountKey(kpid));
provideStoredQueue(kpid).delete();
}
/**
* Obtain a KRef for the next unallocated kernel promise.
*
* @returns The next kpid use.
*/
function getNextPromiseId() {
return (0, kernel_slots_ts_1.makeKernelSlot)('promise', incCounter(ctx.nextPromiseId));
}
/**
* Add a new subscriber to a kernel promise's collection of subscribers.
*
* @param vatId - The vat that is subscribing.
* @param kpid - The KRef of the promise being subscribed to.
*/
function addPromiseSubscriber(vatId, kpid) {
(0, types_ts_1.insistVatId)(vatId);
const kp = getKernelPromise(kpid);
kp.state === 'unresolved' ||
(0, errors_1.Fail) `attempt to add subscriber to resolved promise ${kpid}`;
const tempSet = new Set(kp.subscribers);
tempSet.add(vatId);
const newSubscribers = Array.from(tempSet).sort();
const key = `${kpid}.subscribers`;
ctx.kv.set(key, JSON.stringify(newSubscribers));
}
/**
* Assign a kernel promise's decider.
*
* @param kpid - The KRef of promise whose decider is being set.
* @param vatId - The vat which will become the decider.
*/
function setPromiseDecider(kpid, vatId) {
if (vatId !== 'kernel') {
(0, types_ts_1.insistVatId)(vatId);
}
if (kpid) {
ctx.kv.set(`${kpid}.decider`, vatId);
}
}
/**
* Record the resolution of a kernel promise.
*
* @param kpid - The ref of the promise being resolved.
* @param rejected - True if the promise is being rejected, false if fulfilled.
* @param value - The value the promise is being fulfilled to or rejected with.
*/
function resolveKernelPromise(kpid, rejected, value) {
const queue = provideStoredQueue(kpid, false);
for (const message of getKernelPromiseMessageQueue(kpid)) {
const messageItem = {
type: 'send',
target: kpid,
message,
};
enqueueRun(messageItem);
}
ctx.kv.set(`${kpid}.state`, rejected ? 'rejected' : 'fulfilled');
ctx.kv.set(`${kpid}.value`, JSON.stringify(value));
ctx.kv.delete(`${kpid}.decider`);
ctx.kv.delete(`${kpid}.subscribers`);
// Drop the baseline “decider” refcount now that the promise is settled.
decrementRefCount(kpid, 'resolve|decider');
queue.delete();
}
/**
* Append a message to a promise's message queue.
*
* @param kpid - The KRef of the promise to enqueue on.
* @param message - The message to enqueue.
*/
function enqueuePromiseMessage(kpid, message) {
provideStoredQueue(kpid, false).enqueue(message);
}
/**
* Fetch the messages in a kernel promise's message queue.
*
* @param kpid - The KRef of the kernel promise of interest.
* @returns An array of all the messages in the given promise's message queue.
*/
function getKernelPromiseMessageQueue(kpid) {
const result = [];
const queue = provideStoredQueue(kpid, false);
for (;;) {
const message = queue.dequeue();
if (message) {
result.push(message);
}
else {
return result;
}
}
}
/**
* Generator that yield the promises decided by a given vat.
*
* @param decider - The vat ID of the vat of interest.
*
* @yields the kpids of all the promises decided by `decider`.
*/
function* getPromisesByDecider(decider) {
const basePrefix = `cle.${decider}.`;
for (const key of getPrefixedKeys(`${basePrefix}p`)) {
const kpid = ctx.kv.getRequired(key);
const kp = getKernelPromise(kpid);
if (kp.state === 'unresolved' && kp.decider === decider) {
yield kpid;
}
}
}
/**
* Given a promise that has just been resolved and the value it resolved to,
* find all promises reachable (recursively) from the new resolution value
* which are themselves already resolved. This will determine the set of
* resolutions that subscribers to the original promise will need to be
* notified of.
*
* This is needed because subscription to a promise carries with it an implied
* subscription to any promises that appear in its resolution value -- these
* subscriptions must be implied rather than explicit because they are
* necessarily unknown at the time of the original promise was subscribed to.
*
* @param origKpid - The original promise to start from.
* @param origValue - The value the original promise is resolved to.
* @returns An array of the kpids of the promises whose values become visible
* as a consequence of the resolution of `origKpid`.
*/
function getKpidsToRetire(origKpid, origValue) {
const seen = new Set();
const scanPromise = (kpid, value) => {
seen.add(kpid);
if (value) {
for (const slot of value.slots) {
if ((0, promise_ref_ts_1.isPromiseRef)(slot)) {
if (!seen.has(slot)) {
const promise = getKernelPromise(slot);
if (promise.state !== 'unresolved') {
if (promise.value) {
scanPromise(slot, promise.value);
}
}
}
}
}
}
};
scanPromise(origKpid, origValue);
return Array.from(seen);
}
return {
initKernelPromise,
getKernelPromise,
deleteKernelPromise,
getNextPromiseId,
addPromiseSubscriber,
setPromiseDecider,
resolveKernelPromise,
enqueuePromiseMessage,
getKernelPromiseMessageQueue,
getPromisesByDecider,
getKpidsToRetire,
};
}
//# sourceMappingURL=promise.cjs.map