Skip to content

Commit

Permalink
Merge pull request #1121 from Agoric/retire-resolved-kernel-promises
Browse files Browse the repository at this point in the history
Implement kernel promise reference counting
  • Loading branch information
FUDCo authored May 27, 2020
2 parents 904b3a0 + 25a36f0 commit a2d8a2c
Show file tree
Hide file tree
Showing 18 changed files with 702 additions and 34 deletions.
27 changes: 27 additions & 0 deletions packages/SwingSet/src/kernel/kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,19 @@ export default function buildKernel(kernelEndowments) {
parseKernelSlot(target);
insistMessage(msg);
const m = harden({ type: 'send', target, msg });
kernelKeeper.incrementRefCount(target, `enq|msg|t`);
kernelKeeper.incrementRefCount(msg.result, `enq|msg|r`);
let idx = 0;
for (const argSlot of msg.args.slots) {
kernelKeeper.incrementRefCount(argSlot, `enq|msg|s${idx}`);
idx += 1;
}
kernelKeeper.addToRunQueue(m);
}

function notify(vatID, kpid) {
const m = harden({ type: 'notify', vatID, kpid });
kernelKeeper.incrementRefCount(kpid, `enq|notify`);
kernelKeeper.addToRunQueue(m);
}

Expand Down Expand Up @@ -180,6 +188,11 @@ export default function buildKernel(kernelEndowments) {
insistCapData(data);
const p = getResolveablePromise(kpid, vatID);
const { subscribers, queue } = p;
let idx = 0;
for (const dataSlot of data.slots) {
kernelKeeper.incrementRefCount(dataSlot, `fulfill|s${idx}`);
idx += 1;
}
kernelKeeper.fulfillKernelPromiseToData(kpid, data);
notifySubscribersAndQueue(kpid, vatID, subscribers, queue);
}
Expand All @@ -190,6 +203,11 @@ export default function buildKernel(kernelEndowments) {
insistCapData(data);
const p = getResolveablePromise(kpid, vatID);
const { subscribers, queue } = p;
let idx = 0;
for (const dataSlot of data.slots) {
kernelKeeper.incrementRefCount(dataSlot, `reject|s${idx}`);
idx += 1;
}
kernelKeeper.rejectKernelPromise(kpid, data);
notifySubscribersAndQueue(kpid, vatID, subscribers, queue);
}
Expand Down Expand Up @@ -387,12 +405,21 @@ export default function buildKernel(kernelEndowments) {
try {
processQueueRunning = Error('here');
if (message.type === 'send') {
kernelKeeper.decrementRefCount(message.target, `deq|msg|t`);
kernelKeeper.decrementRefCount(message.msg.result, `deq|msg|r`);
let idx = 0;
for (const argSlot of message.msg.args.slots) {
kernelKeeper.decrementRefCount(argSlot, `deq|msg|s${idx}`);
idx += 1;
}
await deliverToTarget(message.target, message.msg);
} else if (message.type === 'notify') {
kernelKeeper.decrementRefCount(message.kpid, `deq|notify`);
await processNotify(message);
} else {
throw Error(`unable to process message.type ${message.type}`);
}
kernelKeeper.purgeDeadKernelPromises();
commitCrank();
kernelKeeper.incrementCrankNumber();
} finally {
Expand Down
93 changes: 88 additions & 5 deletions packages/SwingSet/src/kernel/state/kernelKeeper.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { insistCapData } from '../../capdata';
import { insistDeviceID, insistVatID, makeDeviceID, makeVatID } from '../id';
import { kdebug } from '../kdebug';

const enableKernelPromiseGC = true;

// This holds all the kernel state, including that of each Vat and Device, in
// a single JSON-serializable object. At any moment (well, really only
// between turns) we might be asked for it as a string. This same string may
Expand Down Expand Up @@ -177,6 +179,7 @@ export default function makeKernelKeeper(storage) {
storage.set(`${s}.decider`, deciderVatID);
storage.set(`${s}.subscribers`, '');
storage.set(`${s}.queue.nextID`, `0`);
storage.set(`${s}.refCount`, `0`);
// queue is empty, so no state[kp$NN.queue.$NN] keys yet
return s;
}
Expand All @@ -188,6 +191,7 @@ export default function makeKernelKeeper(storage) {
case undefined:
throw new Error(`unknown kernelPromise '${kernelSlot}'`);
case 'unresolved':
p.refCount = Number(storage.get(`${kernelSlot}.refCount`));
p.decider = storage.get(`${kernelSlot}.decider`);
if (p.decider === '') {
p.decider = undefined;
Expand All @@ -198,17 +202,20 @@ export default function makeKernelKeeper(storage) {
).map(JSON.parse);
break;
case 'fulfilledToPresence':
p.refCount = Number(storage.get(`${kernelSlot}.refCount`));
p.slot = storage.get(`${kernelSlot}.slot`);
parseKernelSlot(p.slot);
break;
case 'fulfilledToData':
p.refCount = Number(storage.get(`${kernelSlot}.refCount`));
p.data = {
body: storage.get(`${kernelSlot}.data.body`),
slots: commaSplit(storage.get(`${kernelSlot}.data.slots`)),
};
p.data.slots.map(parseKernelSlot);
break;
case 'rejected':
p.refCount = Number(storage.get(`${kernelSlot}.refCount`));
p.data = {
body: storage.get(`${kernelSlot}.data.body`),
slots: commaSplit(storage.get(`${kernelSlot}.data.slots`)),
Expand All @@ -226,8 +233,7 @@ export default function makeKernelKeeper(storage) {
return storage.has(`${kernelSlot}.state`);
}

function deleteKernelPromise(kpid) {
// storage.deleteRange(`${kpid}.`, `${kpid}/`);
function deleteKernelPromiseState(kpid) {
storage.delete(`${kpid}.state`);
storage.delete(`${kpid}.decider`);
storage.delete(`${kpid}.subscribers`);
Expand All @@ -238,17 +244,22 @@ export default function makeKernelKeeper(storage) {
storage.delete(`${kpid}.data.slots`);
}

function deleteKernelPromise(kpid) {
deleteKernelPromiseState(kpid);
storage.delete(`${kpid}.refCount`);
}

function fulfillKernelPromiseToPresence(kernelSlot, targetSlot) {
insistKernelType('promise', kernelSlot);
deleteKernelPromise(kernelSlot);
deleteKernelPromiseState(kernelSlot);
storage.set(`${kernelSlot}.state`, 'fulfilledToPresence');
storage.set(`${kernelSlot}.slot`, targetSlot);
}

function fulfillKernelPromiseToData(kernelSlot, capdata) {
insistKernelType('promise', kernelSlot);
insistCapData(capdata);
deleteKernelPromise(kernelSlot);
deleteKernelPromiseState(kernelSlot);
storage.set(`${kernelSlot}.state`, 'fulfilledToData');
storage.set(`${kernelSlot}.data.body`, capdata.body);
storage.set(`${kernelSlot}.data.slots`, capdata.slots.join(','));
Expand All @@ -257,7 +268,7 @@ export default function makeKernelKeeper(storage) {
function rejectKernelPromise(kernelSlot, capdata) {
insistKernelType('promise', kernelSlot);
insistCapData(capdata);
deleteKernelPromise(kernelSlot);
deleteKernelPromiseState(kernelSlot);
storage.set(`${kernelSlot}.state`, 'rejected');
storage.set(`${kernelSlot}.data.body`, capdata.body);
storage.set(`${kernelSlot}.data.slots`, capdata.slots.join(','));
Expand Down Expand Up @@ -355,6 +366,73 @@ export default function makeKernelKeeper(storage) {
return storage.get(k);
}

const deadKernelPromises = new Set();

/**
* Increment the reference count associated with some kernel object.
*
* Note that currently we are only reference counting promises, but ultimately
* we intend to keep track of all objects with kernel slots.
*
* @param kernelSlot The kernel slot whose refcount is to be incremented.
*/
function incrementRefCount(kernelSlot, tag) {
if (kernelSlot && parseKernelSlot(kernelSlot).type === 'promise') {
const refCount = Nat(Number(storage.get(`${kernelSlot}.refCount`))) + 1;
kdebug(`++ ${kernelSlot} ${tag} ${refCount}`);
storage.set(`${kernelSlot}.refCount`, `${refCount}`);
}
}

/**
* Decrement the reference count associated with some kernel object.
*
* Note that currently we are only reference counting promises, but ultimately
* we intend to keep track of all objects with kernel slots.
*
* @param kernelSlot The kernel slot whose refcount is to be decremented.
*
* @return true if the reference count has been decremented to zero, false if it is still non-zero
*
* @throws if this tries to decrement the reference count below zero.
*/
function decrementRefCount(kernelSlot, tag) {
if (kernelSlot && parseKernelSlot(kernelSlot).type === 'promise') {
let refCount = Nat(Number(storage.get(`${kernelSlot}.refCount`)));
assert(refCount > 0, details`refCount underflow {kernelSlot} ${tag}`);
refCount -= 1;
kdebug(`-- ${kernelSlot} ${tag} ${refCount}`);
storage.set(`${kernelSlot}.refCount`, `${refCount}`);
if (refCount === 0) {
deadKernelPromises.add(kernelSlot);
return true;
}
}
return false;
}

function purgeDeadKernelPromises() {
if (enableKernelPromiseGC) {
for (const kpid of deadKernelPromises.values()) {
const kp = getKernelPromise(kpid);
if (kp.refCount === 0) {
if (kp.state === 'fulfilledToData' || kp.state === 'rejected') {
let idx = 0;
for (const slot of kp.data.slots) {
// Note: the following decrement can result in an addition to the
// deadKernelPromises set, which we are in the midst of iterating.
// TC39 went to a lot of trouble to ensure that this is kosher.
decrementRefCount(slot, `gc|${kpid}|s${idx}`);
idx += 1;
}
}
deleteKernelPromise(kpid);
}
}
}
deadKernelPromises.clear();
}

function allocateVatKeeperIfNeeded(vatID) {
insistVatID(vatID);
if (!storage.has(`${vatID}.o.nextID`)) {
Expand All @@ -366,6 +444,8 @@ export default function makeKernelKeeper(storage) {
vatID,
addKernelObject,
addKernelPromise,
incrementRefCount,
decrementRefCount,
);
ephemeral.vatKeepers.set(vatID, vk);
}
Expand Down Expand Up @@ -520,6 +600,7 @@ export default function makeKernelKeeper(storage) {

getCrankNumber,
incrementCrankNumber,
purgeDeadKernelPromises,

ownerOfKernelObject,
ownerOfKernelDevice,
Expand All @@ -534,6 +615,8 @@ export default function makeKernelKeeper(storage) {
addSubscriberToPromise,
setDecider,
clearDecider,
incrementRefCount,
decrementRefCount,

addToRunQueue,
isRunQueueEmpty,
Expand Down
11 changes: 9 additions & 2 deletions packages/SwingSet/src/kernel/state/vatKeeper.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export function makeVatKeeper(
vatID,
addKernelObject,
addKernelPromise,
incrementRefCount,
decrementRefCount,
) {
insistVatID(vatID);

Expand Down Expand Up @@ -81,6 +83,7 @@ export function makeVatKeeper(
} else {
throw new Error(`unknown type ${type}`);
}
incrementRefCount(kernelSlot, `${vatID}|vk|clist`);
const kernelKey = `${vatID}.c.${kernelSlot}`;
storage.set(kernelKey, vatSlot);
storage.set(vatKey, kernelSlot);
Expand Down Expand Up @@ -125,6 +128,7 @@ export function makeVatKeeper(
} else {
throw new Error(`unknown type ${type}`);
}
incrementRefCount(kernelSlot, `${vatID}[kv|clist`);
const vatSlot = makeVatSlot(type, false, id);

const vatKey = `${vatID}.c.${vatSlot}`;
Expand All @@ -148,8 +152,11 @@ export function makeVatKeeper(
const kernelKey = `${vatID}.c.${kernelSlot}`;
const vatKey = `${vatID}.c.${vatSlot}`;
kdebug(`Delete mapping ${kernelKey}<=>${vatKey}`);
storage.delete(kernelKey);
storage.delete(vatKey);
if (storage.has(kernelKey)) {
decrementRefCount(kernelSlot, `${vatID}|del|clist`);
storage.delete(kernelKey);
storage.delete(vatKey);
}
}

/**
Expand Down
4 changes: 3 additions & 1 deletion packages/SwingSet/src/kernel/vatManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,16 @@ export default function makeVatManager(
}

function deleteCListEntryIfEasy(kpid, vpid, kernelData) {
let idx = 0;
for (const slot of kernelData.slots) {
const { type } = parseKernelSlot(slot);
if (type === 'promise') {
kdebug(
`Unable to delete ${vatID} clist entry ${kpid}<=>${vpid} because slot ${slot} is a promise`,
`Unable to delete ${vatID} clist entry ${kpid}<=>${vpid} because slot[${idx}]===${slot} is a promise`,
);
return;
}
idx += 1;
}
vatKeeper.deleteCListEntry(kpid, vpid);
}
Expand Down
Loading

0 comments on commit a2d8a2c

Please sign in to comment.