Skip to content

Commit

Permalink
feat(swingset): comms: add/manipulate isReachable flag
Browse files Browse the repository at this point in the history
Each c-list object entry now has an `isReachable` flag. On import-facing
c-lists, this indicates that the downstream (kernel or remote) can reach the
object (and is cleared when the downstream side drops their strong
reference). On the one export-facing c-list, we use the flag to remember
whether we've dropped our import or not.

c-list translators now ensure the flag is set after an allocation-worthy
reference (imports during inbound translation, exports during outbound
translation) is converted. C-list entry deletion functions ensure the flag is
clear. In either case, if the state of the flag actually changed (on an
import-side c-list), the `reachable` refcount is modified. If that refcount
touches zero, the object is marked as "maybe free" for later investigation.

`addEgress` also sets the isReachable flag, to reflect the initial conditions
of a manually-added c-list entry.
  • Loading branch information
warner committed Jun 20, 2021
1 parent 98a2038 commit 133bbae
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 21 deletions.
32 changes: 28 additions & 4 deletions packages/SwingSet/src/vats/comms/clist-inbound.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,12 @@ export function makeInbound(state) {

function getLocalForRemote(remoteID, rref) {
const remote = state.getRemote(remoteID);
const lref = remote.mapFromRemote(rref);
const { mapFromRemote, isReachable } = remote;
const lref = mapFromRemote(rref);
assert(lref, X`${rref} must already be in remote ${rname(remote)}`);
if (parseRemoteSlot(rref).type === 'object') {
assert(isReachable(lref), `remote sending to unreachable ${lref}`);
}
return lref;
}

Expand Down Expand Up @@ -93,17 +97,37 @@ export function makeInbound(state) {
// previously, or if we're the ones who sent it to them earlier, it will be
// in the inbound table already.
const remote = state.getRemote(remoteID);
if (!remote.mapFromRemote(rref)) {
const { type } = parseRemoteSlot(rref);
const { type, allocatedByRecipient } = parseRemoteSlot(rref);
// !allocatedByRecipient means we're willing to allocate
let lref = remote.mapFromRemote(rref);
if (!lref) {
if (type === 'object') {
addLocalObjectForRemote(remote, rref);
} else if (type === 'promise') {
addLocalPromiseForRemote(remote, rref);
} else {
assert.fail(X`cannot accept type ${type} from remote`);
}
lref = remote.mapFromRemote(rref);
}

// in either case, we need to mark imports or re-imports as reachable
if (type === 'object') {
// Senders are very polite and always translate rrefs into the
// recipient's number space. So if we receive ro-2 from the remote
// (!allocatedByRecipient), that means it was allocated by the remote,
// and this is an exporting reference. We're willing to allocate an
// lref on this inbound pathway.
const doSetReachable = !allocatedByRecipient;
if (doSetReachable) {
// the remote is exporting, not importing
const isImport = false;
remote.setReachable(lref, isImport);
}
assert(remote.isReachable(lref), `remote using unreachable ${lref}`);
}
return remote.mapFromRemote(rref);

return lref;
}

function provideLocalForRemoteResult(remoteID, result) {
Expand Down
52 changes: 46 additions & 6 deletions packages/SwingSet/src/vats/comms/clist-kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,25 @@ export function makeKernel(state, syscall) {
// been retired or not, and create a new (short-lived) identifier to reference
// resolved promises if necessary.

const isReachable = state.isReachableByKernel;
const setReachable = state.setReachableByKernel;
// const clearReachable = state.clearReachableByKernel;

function getKernelForLocal(lref) {
const kfref = state.mapToKernel(lref);
assert(kfref, X`${lref} must already be mapped to a kernel-facing ID`);
const { type, allocatedByVat } = parseVatSlot(kfref);
if (type === 'object' && !allocatedByVat) {
// comms import, kernel export, make sure we can stil reach it
assert(isReachable(lref), X`comms sending to unreachable import ${lref}`);
}
return kfref;
}

function provideKernelForLocal(lref) {
if (!state.mapToKernel(lref)) {
let kfref;
const { type } = parseLocalSlot(lref);

const { type } = parseLocalSlot(lref);
let kfref = state.mapToKernel(lref);
if (!kfref) {
if (type === 'object') {
kfref = state.allocateKernelObjectID();
} else if (type === 'promise') {
Expand All @@ -44,7 +52,21 @@ export function makeKernel(state, syscall) {
state.addKernelMapping(kfref, lref);
cdebug(`comms add mapping l->k ${kfref}<=>${lref}`);
}
return state.mapToKernel(lref);

if (type === 'object') {
// we setReachable in the same cases that we're willing to allocate a
// kfref
const { allocatedByVat } = parseVatSlot(kfref);
const doSetReachable = allocatedByVat;
if (doSetReachable) {
// the kernel is an importer in this case
const isImport = true;
setReachable(lref, isImport);
}
assert(isReachable(lref), X`comms sending unreachable ${lref}`);
}

return kfref;
}

function provideKernelForLocalResult(lpid) {
Expand Down Expand Up @@ -81,6 +103,13 @@ export function makeKernel(state, syscall) {
function getLocalForKernel(kfref) {
const lref = state.mapFromKernel(kfref);
assert(lref, X`${kfref} must already be mapped to a local ID`);
if (parseVatSlot(kfref).type === 'object') {
// comms export, kernel import, it must be reachable
assert(
isReachable(lref),
X`kernel sending to unreachable import ${lref}`,
);
}
return lref;
}

Expand Down Expand Up @@ -123,15 +152,26 @@ export function makeKernel(state, syscall) {
assert.fail(X`cannot accept type ${type} from kernel`);
}
}

const lref = state.mapFromKernel(kfref);

if (type === 'promise') {
if (!allocatedByVat) {
if (!doNotSubscribeSet || !doNotSubscribeSet.has(kfref)) {
syscall.subscribe(kfref);
}
}
} else if (type === 'object') {
// we setReachable in the same cases that we're willing to allocate an
// lref
const doSetReachable = !allocatedByVat;
if (doSetReachable) {
// the kernel is an exporter in this case
const isImport = false;
setReachable(lref, isImport);
}
assert(isReachable(lref), `kernel using unreachable ${lref}`);
}

return lref;
}

Expand Down
34 changes: 29 additions & 5 deletions packages/SwingSet/src/vats/comms/clist-outbound.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { assert, details as X } from '@agoric/assert';
import { parseLocalSlot, insistLocalType } from './parseLocalSlots.js';
import { flipRemoteSlot, insistRemoteType } from './parseRemoteSlot.js';
import {
flipRemoteSlot,
insistRemoteType,
parseRemoteSlot,
} from './parseRemoteSlot.js';
import { cdebug } from './cdebug.js';

function rname(remote) {
Expand All @@ -10,8 +14,12 @@ function rname(remote) {
export function makeOutbound(state) {
function getRemoteForLocal(remoteID, lref) {
const remote = state.getRemote(remoteID);
const rref = remote.mapToRemote(lref);
const { mapToRemote, isReachable } = remote;
const rref = mapToRemote(lref);
assert(rref, X`${lref} must already be in remote ${rname(remote)}`);
if (parseRemoteSlot(rref).type === 'object') {
assert(isReachable(lref), `sending unreachable ${lref} to remote`);
}
return rref;
}

Expand Down Expand Up @@ -52,17 +60,33 @@ export function makeOutbound(state) {
// or if they're the ones who sent it to us in the first place, it will be
// in the outbound table already.
const remote = state.getRemote(remoteID);
if (!remote.mapToRemote(lref)) {
const { type } = parseLocalSlot(lref);
const { type } = parseLocalSlot(lref);
let rref = remote.mapToRemote(lref);
if (!rref) {
if (type === 'object') {
addRemoteObjectForLocal(remote, lref);
} else if (type === 'promise') {
addRemotePromiseForLocal(remote, lref);
} else {
assert.fail(X`cannot send type ${type} to remote`);
}
rref = remote.mapToRemote(lref);
}
const rref = remote.mapToRemote(lref);

// in either case, we need to mark exports or re-exports as reachable
if (type === 'object') {
const { allocatedByRecipient } = parseRemoteSlot(rref);
// `rref` is what the remote wants to hear, so allocatedByRecipient
// means they exported it, !allocatedByRecipient means we did
const doSetReachable = !allocatedByRecipient;
if (doSetReachable) {
// the remote is always importing it
const isImport = true;
remote.setReachable(lref, isImport);
}
assert(remote.isReachable(lref), `sending unreachable rref ${lref}`);
}

return rref;
}

Expand Down
3 changes: 3 additions & 0 deletions packages/SwingSet/src/vats/comms/clist-xgress.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export function makeIngressEgress(state, provideLocalForRemote) {
const inboundRRef = makeRemoteSlot('object', true, remoteRefID);
remote.addRemoteMapping(inboundRRef, loid);
remote.skipRemoteObjectID(remoteRefID);
const isCommsImport = false;
remote.setReachable(loid, isCommsImport);

// prettier-ignore
cdebug(`comms add egress ${loid} to ${remoteID} in ${inboundRRef}`);
}
Expand Down
56 changes: 53 additions & 3 deletions packages/SwingSet/src/vats/comms/remote.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Nat } from '@agoric/nat';
import { assert, details as X } from '@agoric/assert';
import { makeRemoteSlot, flipRemoteSlot } from './parseRemoteSlot.js';
import { parseLocalSlot } from './parseLocalSlots.js';
import {
makeRemoteSlot,
flipRemoteSlot,
parseRemoteSlot,
} from './parseRemoteSlot.js';

export function insistRemoteID(remoteID) {
assert(/^r\d+$/.test(remoteID), X`not a remoteID: ${remoteID}`);
Expand Down Expand Up @@ -51,22 +56,62 @@ export function makeRemote(state, store, remoteID) {
return store.get(`${remoteID}.c.${lref}`);
}

// is/set/clear are used on both imports and exports, but set/clear needs
// to be told which one it is

function isReachable(lref) {
assert.equal(parseLocalSlot(lref).type, 'object');
return !!store.get(`${remoteID}.cr.${lref}`);
}
function setReachable(lref, isImport) {
const wasReachable = isReachable(lref);
if (!wasReachable) {
store.set(`${remoteID}.cr.${lref}`, `1`);
if (isImport) {
state.changeReachable(lref, 1n);
}
}
}
function clearReachable(lref, isImport) {
const wasReachable = isReachable(lref);
if (wasReachable) {
store.delete(`${remoteID}.cr.${lref}`);
if (isImport) {
const reachable = state.changeReachable(lref, -1n);
if (!reachable) {
state.lrefMightBeFree(lref);
}
}
}
}

// rref is what we would get from them, so + means our export
function addRemoteMapping(rref, lref) {
const { type, allocatedByRecipient } = parseRemoteSlot(rref);
const isImport = allocatedByRecipient;
const fromKey = `${remoteID}.c.${rref}`;
const toKey = `${remoteID}.c.${lref}`;
assert(!store.get(fromKey), X`already have ${rref}`);
assert(!store.get(toKey), X`already have ${lref}`);
store.set(fromKey, lref);
store.set(toKey, flipRemoteSlot(rref));
state.incrementRefCount(lref, `{rref}|${remoteID}|clist`);
const mode = isImport ? 'clist-import' : 'clist-export';
state.incrementRefCount(lref, `{rref}|${remoteID}|clist`, mode);
}

function deleteRemoteMapping(lref) {
const rrefOutbound = store.get(`${remoteID}.c.${lref}`);
const rrefInbound = flipRemoteSlot(rrefOutbound);
let mode = 'data'; // close enough
const { type, allocatedByRecipient } = parseRemoteSlot(rrefInbound);
const isImport = allocatedByRecipient;
if (type === 'object') {
clearReachable(lref, isImport);
mode = isImport ? 'clist-import' : 'clist-export';
}
store.delete(`${remoteID}.c.${rrefInbound}`);
store.delete(`${remoteID}.c.${lref}`);
state.decrementRefCount(lref, `{rref}|${remoteID}|clist`);
state.decrementRefCount(lref, `{rref}|${remoteID}|clist`, mode);
}

function nextSendSeqNum() {
Expand Down Expand Up @@ -144,10 +189,15 @@ export function makeRemote(state, store, remoteID) {
return harden({
remoteID: () => remoteID,
name,

mapFromRemote,
mapToRemote,
isReachable,
setReachable,
clearReachable,
addRemoteMapping,
deleteRemoteMapping,

allocateRemoteObject,
skipRemoteObjectID,
allocateRemotePromise,
Expand Down
Loading

0 comments on commit 133bbae

Please sign in to comment.