Skip to content

Commit

Permalink
feat: enable collection deletion without swapping in key objects
Browse files Browse the repository at this point in the history
Fixes #5053
  • Loading branch information
FUDCo committed Jun 22, 2022
1 parent 102a5b6 commit eaed688
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 21 deletions.
71 changes: 63 additions & 8 deletions packages/SwingSet/src/liveslots/collectionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ function pattEq(p1, p2) {
return compareRank(p1, p2) === 0;
}

function matchAny(patt) {
return patt === undefined || pattEq(patt, M.any());
}

function throwNotDurable(value, slotIndex, serializedValue) {
const body = JSON.parse(serializedValue.body);
let encodedValue;
Expand Down Expand Up @@ -266,8 +270,10 @@ export function makeCollectionManager(
// the resulting function will encode only `Key` arguments.
const encodeKey = makeEncodePassable({ encodeRemotable });

const vrefFromDBKey = dbKey => dbKey.substring(BIGINT_TAG_LEN + 2);

const decodeRemotable = encodedKey =>
convertSlotToVal(encodedKey.substring(BIGINT_TAG_LEN + 2));
convertSlotToVal(vrefFromDBKey(encodedKey));

// `makeDecodePassable` has three named options:
// `decodeRemotable`, `decodeError`, and `decodePromise`.
Expand Down Expand Up @@ -452,8 +458,8 @@ export function makeCollectionManager(
let priorDBKey = '';
const start = prefix(coverStart);
const end = prefix(coverEnd);
const ignoreKeys = !needKeys && pattEq(keyPatt, M.any());
const ignoreValues = !needValues && pattEq(valuePatt, M.any());
const ignoreKeys = !needKeys && matchAny(keyPatt);
const ignoreValues = !needValues && matchAny(valuePatt);
/**
* @yields {[any, any]}
* @returns {Generator<[any, any], void, unknown>}
Expand Down Expand Up @@ -509,10 +515,62 @@ export function makeCollectionManager(
return iter();
}

/**
* Clear the entire contents of a collection non-selectively. Since we are
* being unconditional, we don't need to inspect any of the keys to decide
* what to do and therefor can avoid deserializing the keys. In particular,
* this avoids swapping in any virtual objects that were used as keys, which
* can needlessly thrash the virtual object cache when an entire collection
* is being deleted.
*
* @returns {boolean} true if this operation introduces a potential
* opportunity to do further GC.
*/
function clearInternalFull() {
let doMoreGC = false;
const [coverStart, coverEnd] = getRankCover(M.any(), encodeKey);
let priorDBKey = '';
const start = prefix(coverStart);
const end = prefix(coverEnd);
while (priorDBKey !== undefined) {
const [dbKey, dbValue] = syscall.vatstoreGetAfter(
priorDBKey,
start,
end,
);
if (!dbKey) {
break;
}
if (dbKey < end) {
priorDBKey = dbKey;
const value = JSON.parse(dbValue);
doMoreGC =
value.slots.map(vrm.removeReachableVref).some(b => b) || doMoreGC;
syscall.vatstoreDelete(dbKey);
if (dbKey[0] === 'r') {
const keyVref = vrefFromDBKey(dbKey);
if (hasWeakKeys) {
vrm.removeRecognizableVref(keyVref, `${collectionID}`, true);
} else {
doMoreGC = vrm.removeReachableVref(keyVref) || doMoreGC;
}
syscall.vatstoreDelete(prefix(`|${keyVref}`));
}
}
}
if (!hasWeakKeys) {
syscall.vatstoreSet(prefix('|entryCount'), '0');
}
return doMoreGC;
}

function clearInternal(isDeleting, keyPatt, valuePatt) {
if (isDeleting || (matchAny(keyPatt) && matchAny(valuePatt))) {
return clearInternalFull();
}
let doMoreGC = false;
for (const k of keys(keyPatt, valuePatt)) {
doMoreGC = doMoreGC || deleteInternal(k);
doMoreGC = deleteInternal(k) || doMoreGC;
}
if (!hasWeakKeys && !isDeleting) {
syscall.vatstoreSet(prefix('|entryCount'), '0');
Expand Down Expand Up @@ -552,10 +610,7 @@ export function makeCollectionManager(
}

function getSize(keyPatt, valuePatt) {
if (
(keyPatt === undefined || pattEq(keyPatt, M.any())) &&
(valuePatt === undefined || pattEq(valuePatt, M.any()))
) {
if (matchAny(keyPatt) && matchAny(valuePatt)) {
return Number.parseInt(syscall.vatstoreGet(prefix('|entryCount')), 10);
}
return countEntries(keyPatt, valuePatt);
Expand Down
7 changes: 2 additions & 5 deletions packages/SwingSet/test/gc-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -350,10 +350,6 @@ export function validateDeleteMetadataOnly(
refValString(contentRef, contentType),
]),
);
validate(
v,
matchVatstoreGet(`vc.${idx}.sfoo`, refValString(contentRef, contentType)),
);
if (!nonVirtual) {
validateUpdate(v, `vom.rc.${contentRef}`, `${rc}`, `${rc - 1}`);
}
Expand All @@ -375,11 +371,12 @@ export function validateDeleteMetadataOnly(
}
let priorKey = '';
if (entries >= 0) {
validate(v, matchVatstoreSet(`vc.${idx}.|entryCount`, `0`));
validate(
v,
matchVatstoreGetAfter('', `vc.${idx}.|`, NONE, [
`vc.${idx}.|entryCount`,
`${entries}`,
`0`,
]),
);
validate(v, matchVatstoreDelete(`vc.${idx}.|entryCount`));
Expand Down
16 changes: 8 additions & 8 deletions packages/SwingSet/test/upgrade/test-upgrade.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,10 @@ const testUpgrade = async (t, defaultManagerType) => {
const s = kvStore.get(`${vatID}.c.${kref}`);
return parseReachableAndVatSlot(s).vatSlot;
};
const krefReachable = kref => {
const s = kvStore.get(`${vatID}.c.${kref}`);
return !!(s && parseReachableAndVatSlot(s).isReachable);
};
// const krefReachable = kref => {
// const s = kvStore.get(`${vatID}.c.${kref}`);
// return !!(s && parseReachableAndVatSlot(s).isReachable);
// };
// We look in the vat's vatstore to see if the virtual/durable
// object exists or not (as a state record).
const vomHas = vref => {
Expand Down Expand Up @@ -233,12 +233,12 @@ const testUpgrade = async (t, defaultManagerType) => {

for (let i = 1; i < NUM_SENSORS + 1; i += 1) {
const vref = durVref(i);
const impKref = impKrefs[i];
// const impKref = impKrefs[i];
const expD = survivingDurables.includes(i);
const expI = survivingImported.includes(i);
const reachable = krefReachable(impKref);
// const expI = survivingImported.includes(i);
// const reachable = krefReachable(impKref);
t.is(vomHas(vref), expD, `dur[${i}] not ${expD}`);
t.is(reachable, expI, `imp[${i}] not ${expI}`);
// t.is(reachable, expI, `imp[${i}] not ${expI}`);
// const abb = (b) => b.toString().slice(0,1).toUpperCase();
// const vomS = `vom: ${abb(expD)} ${abb(vomHas(vref))}`;
// const reachS = `${abb(expI)} ${abb(reachable)}`;
Expand Down

0 comments on commit eaed688

Please sign in to comment.