-
Notifications
You must be signed in to change notification settings - Fork 217
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
raw devices: maybe remove deviceSlots, reenable promises, merge companion vat #1346
Comments
Vats which hold device nodes (`d-NN` references) can use `syscall.callNow()` on them, to make a synchronous invocation (which can return data). The outbound arguments and return data are capdata, which is translated through c-lists just like regular `syscall.send()` and promise resolution. However devices do not (currently) handle Promises at all. The kernel-to-device c-list translation will panic the kernel if asked to translate a promise reference (`kpNN`). Vats should not be able to panic the kernel, even if we give them access to a device node. This changes the vat-to-kernel translator to reject promise references in the arguments of `callNow`, making it a vat-fatal error. This will terminate the vat, but leave the kernel running. In the long run (#1346), devices should accept Promises, but it will take more work (and probably require devices to operate on a much lower level than vats do). But this should fix the immediate kernel panic. Note that killing a vat is not exactly friendly either. The bug described in issue #1358 was triggered by user REPL input causing the HTTP vat to try sending a Promise into the Command device, killing the kernel. With this change, this will instead kill the HTTP vat, which breaks the REPL, rendering the system mostly unusable. But at least the attribution is correct. We have another fix in the works that will change liveslots.js to catch this situation during the call to `D(devnode).methname(args)`, which should reduce the blast radius to merely throw an exception in `D()`, rather than killing the whole vat. refs #1358
Vats which hold device nodes (`d-NN` references) can use `syscall.callNow()` on them, to make a synchronous invocation (which can return data). The outbound arguments and return data are capdata, which is translated through c-lists just like regular `syscall.send()` and promise resolution. However devices do not (currently) handle Promises at all. The kernel-to-device c-list translation will panic the kernel if asked to translate a promise reference (`kpNN`). Vats should not be able to panic the kernel, even if we give them access to a device node. This changes the vat-to-kernel translator to reject promise references in the arguments of `callNow`, making it a vat-fatal error. This will terminate the vat, but leave the kernel running. In the long run (#1346), devices should accept Promises, but it will take more work (and probably require devices to operate on a much lower level than vats do). But this should fix the immediate kernel panic. Note that killing a vat is not exactly friendly either. The bug described in issue #1358 was triggered by user REPL input causing the HTTP vat to try sending a Promise into the Command device, killing the kernel. With this change, this will instead kill the HTTP vat, which breaks the REPL, rendering the system mostly unusable. But at least the attribution is correct. We have another fix in the works that will change liveslots.js to catch this situation during the call to `D(devnode).methname(args)`, which should reduce the blast radius to merely throw an exception in `D()`, rather than killing the whole vat. refs #1358
Vats which hold device nodes (`d-NN` references) can use `syscall.callNow()` on them, to make a synchronous invocation (which can return data). The outbound arguments and return data are capdata, which is translated through c-lists just like regular `syscall.send()` and promise resolution. However devices do not (currently) handle Promises at all. The kernel-to-device c-list translation will panic the kernel if asked to translate a promise reference (`kpNN`). Vats should not be able to panic the kernel, even if we give them access to a device node. This changes the vat-to-kernel translator to reject promise references in the arguments of `callNow`, making it a vat-fatal error. This will terminate the vat, but leave the kernel running. In the long run (#1346), devices should accept Promises, but it will take more work (and probably require devices to operate on a much lower level than vats do). But this should fix the immediate kernel panic. Note that killing a vat is not exactly friendly either. The bug described in issue #1358 was triggered by user REPL input causing the HTTP vat to try sending a Promise into the Command device, killing the kernel. With this change, this will instead kill the HTTP vat, which breaks the REPL, rendering the system mostly unusable. But at least the attribution is correct. We have another fix in the works that will change liveslots.js to catch this situation during the call to `D(devnode).methname(args)`, which should reduce the blast radius to merely throw an exception in `D()`, rather than killing the whole vat. refs #1358
This is becoming more interesting as I look at #1848 vat upgrade, in particular the ZCF/contract upgrade flow described in #1848 (comment), because:
We could either add more features to deviceSlots to make it easier to synthesize device nodes on-demand, or we could rip out features to make it easier to accept deliveries for device nodes directly. I'm leaning towards the latter, with an API basically like above. I'll probably use a multi-key My plan is to have an alternative entry point: if the device bundle exports |
I think I'll need a small helper library so Likewise, there should be a helper function to deserialize incoming arguments, and the objects that come out of that deserialization may include a Presence or a Promise. I don't intend to put a lot of methods on the Presence/Promise placeholder objects (in particular I do not intend to make them HandledPromise presences), I just need something from which a vref can be extracted (and stored in the device state somewhere), and which can be used when serializing arguments, naming the target to which we perform a |
"Raw devices" bypass the deviceSlots layer and allow device code direct access to `syscall`, and the arguments arriving through the `dispatch` object it must produce. This makes some patterns much easier to implement, such as producing new device nodes as part of the device's API (e.g. one device node per code bundle). It also provides vatstoreGet/Set/Delete, so the device code can manage one piece of state at a time, instead of doing an expensive read-modify-write cycle on a single large aggregate state object. A helper library named deviceTools.js was added to make it slightly easier to write a raw device. In the longer run (see #1346), we'd like these devices to support Promises and plain object references. This change doesn't go that far. The remaining limitations are: * the deviceTools.js library refuses to handle exported objects, imported foreign device nodes, or promises of any sort * the outbound translator (deviceKeeper.js `mapDeviceSlotToKernelSlot`) refuses to handle exported objects and exported promises * the vat outbound translator (vatTranslator.js `translateCallNow`) refuses to handle promises * liveslots rejects promises in `D()` arguments refs #1346
"Raw devices" bypass the deviceSlots layer and allow device code direct access to `syscall`, and the arguments arriving through the `dispatch` object it must produce. This makes some patterns much easier to implement, such as producing new device nodes as part of the device's API (e.g. one device node per code bundle). It also provides vatstoreGet/Set/Delete, so the device code can manage one piece of state at a time, instead of doing an expensive read-modify-write cycle on a single large aggregate state object. A helper library named deviceTools.js was added to make it slightly easier to write a raw device. In the longer run (see #1346), we'd like these devices to support Promises and plain object references. This change doesn't go that far. The remaining limitations are: * the deviceTools.js library refuses to handle exported objects, imported foreign device nodes, or promises of any sort * the outbound translator (deviceKeeper.js `mapDeviceSlotToKernelSlot`) refuses to handle exported objects and exported promises * the vat outbound translator (vatTranslator.js `translateCallNow`) refuses to handle promises * liveslots rejects promises in `D()` arguments refs #1346
"Raw devices" bypass the deviceSlots layer and allow device code direct access to `syscall`, and the arguments arriving through the `dispatch` object it must produce. This makes some patterns much easier to implement, such as producing new device nodes as part of the device's API (e.g. one device node per code bundle). It also provides vatstoreGet/Set/Delete, so the device code can manage one piece of state at a time, instead of doing an expensive read-modify-write cycle on a single large aggregate state object. A helper library named deviceTools.js was added to make it slightly easier to write a raw device. In the longer run (see #1346), we'd like these devices to support Promises and plain object references. This change doesn't go that far. The remaining limitations are: * the deviceTools.js library refuses to handle exported objects, imported foreign device nodes, or promises of any sort * the outbound translator (deviceKeeper.js `mapDeviceSlotToKernelSlot`) refuses to handle exported objects and exported promises * the vat outbound translator (vatTranslator.js `translateCallNow`) refuses to handle promises * liveslots rejects promises in `D()` arguments refs #1346
"Raw devices" bypass the deviceSlots layer and allow device code direct access to `syscall`, and the arguments arriving through the `dispatch` object it must produce. This makes some patterns much easier to implement, such as producing new device nodes as part of the device's API (e.g. one device node per code bundle). It also provides vatstoreGet/Set/Delete, so the device code can manage one piece of state at a time, instead of doing an expensive read-modify-write cycle on a single large aggregate state object. A helper library named deviceTools.js was added to make it slightly easier to write a raw device. In the longer run (see #1346), we'd like these devices to support Promises and plain object references. This change doesn't go that far. The remaining limitations are: * the deviceTools.js library refuses to handle exported objects, imported foreign device nodes, or promises of any sort * the outbound translator (deviceKeeper.js `mapDeviceSlotToKernelSlot`) refuses to handle exported objects and exported promises * the vat outbound translator (vatTranslator.js `translateCallNow`) refuses to handle promises * liveslots rejects promises in `D()` arguments refs #1346
"Raw devices" bypass the deviceSlots layer and allow device code direct access to `syscall`, and the arguments arriving through the `dispatch` object it must produce. This makes some patterns much easier to implement, such as producing new device nodes as part of the device's API (e.g. one device node per code bundle). It also provides vatstoreGet/Set/Delete, so the device code can manage one piece of state at a time, instead of doing an expensive read-modify-write cycle on a single large aggregate state object. A helper library named deviceTools.js was added to make it slightly easier to write a raw device. In the longer run (see #1346), we'd like these devices to support Promises and plain object references. This change doesn't go that far. The remaining limitations are: * the deviceTools.js library refuses to handle exported objects, imported foreign device nodes, or promises of any sort * the outbound translator (deviceKeeper.js `mapDeviceSlotToKernelSlot`) refuses to handle exported objects and exported promises * the vat outbound translator (vatTranslator.js `translateCallNow`) refuses to handle promises * liveslots rejects promises in `D()` arguments refs #1346
"Raw devices" bypass the deviceSlots layer and allow device code direct access to `syscall`, and the arguments arriving through the `dispatch` object it must produce. This makes some patterns much easier to implement, such as producing new device nodes as part of the device's API (e.g. one device node per code bundle). It also provides vatstoreGet/Set/Delete, so the device code can manage one piece of state at a time, instead of doing an expensive read-modify-write cycle on a single large aggregate state object. A helper library named deviceTools.js was added to make it slightly easier to write a raw device. In the longer run (see #1346), we'd like these devices to support Promises and plain object references. This change doesn't go that far. The remaining limitations are: * the deviceTools.js library refuses to handle exported objects, imported foreign device nodes, or promises of any sort * the outbound translator (deviceKeeper.js `mapDeviceSlotToKernelSlot`) refuses to handle exported objects and exported promises * the vat outbound translator (vatTranslator.js `translateCallNow`) refuses to handle promises * liveslots rejects promises in `D()` arguments refs #1346
"Raw devices" bypass the deviceSlots layer and allow device code direct access to `syscall`, and the arguments arriving through the `dispatch` object it must produce. This makes some patterns much easier to implement, such as producing new device nodes as part of the device's API (e.g. one device node per code bundle). It also provides vatstoreGet/Set/Delete, so the device code can manage one piece of state at a time, instead of doing an expensive read-modify-write cycle on a single large aggregate state object. A helper library named deviceTools.js was added to make it slightly easier to write a raw device. In the longer run (see #1346), we'd like these devices to support Promises and plain object references. This change doesn't go that far. The remaining limitations are: * the deviceTools.js library refuses to handle exported objects, imported foreign device nodes, or promises of any sort * the outbound translator (deviceKeeper.js `mapDeviceSlotToKernelSlot`) refuses to handle exported objects and exported promises * the vat outbound translator (vatTranslator.js `translateCallNow`) refuses to handle promises * liveslots rejects promises in `D()` arguments refs #1346
"Raw devices" bypass the deviceSlots layer and allow device code direct access to `syscall`, and the arguments arriving through the `dispatch` object it must produce. This makes some patterns much easier to implement, such as producing new device nodes as part of the device's API (e.g. one device node per code bundle). It also provides vatstoreGet/Set/Delete, so the device code can manage one piece of state at a time, instead of doing an expensive read-modify-write cycle on a single large aggregate state object. A helper library named deviceTools.js was added to make it slightly easier to write a raw device. In the longer run (see #1346), we'd like these devices to support Promises and plain object references. This change doesn't go that far. The remaining limitations are: * the deviceTools.js library refuses to handle exported objects, imported foreign device nodes, or promises of any sort * the outbound translator (deviceKeeper.js `mapDeviceSlotToKernelSlot`) refuses to handle exported objects and exported promises * the vat outbound translator (vatTranslator.js `translateCallNow`) refuses to handle promises * liveslots rejects promises in `D()` arguments refs #1346
"Raw devices" bypass the deviceSlots layer and allow device code direct access to `syscall`, and the arguments arriving through the `dispatch` object it must produce. This makes some patterns much easier to implement, such as producing new device nodes as part of the device's API (e.g. one device node per code bundle). It also provides vatstoreGet/Set/Delete, so the device code can manage one piece of state at a time, instead of doing an expensive read-modify-write cycle on a single large aggregate state object. A helper library named deviceTools.js was added to make it slightly easier to write a raw device. In the longer run (see #1346), we'd like these devices to support Promises and plain object references. This change doesn't go that far. The remaining limitations are: * the deviceTools.js library refuses to handle exported objects, imported foreign device nodes, or promises of any sort * the outbound translator (deviceKeeper.js `mapDeviceSlotToKernelSlot`) refuses to handle exported objects and exported promises * the vat outbound translator (vatTranslator.js `translateCallNow`) refuses to handle promises * liveslots rejects promises in `D()` arguments refs #1346
"Raw devices" bypass the deviceSlots layer and allow device code direct access to `syscall`, and the arguments arriving through the `dispatch` object it must produce. This makes some patterns much easier to implement, such as producing new device nodes as part of the device's API (e.g. one device node per code bundle). It also provides vatstoreGet/Set/Delete, so the device code can manage one piece of state at a time, instead of doing an expensive read-modify-write cycle on a single large aggregate state object. A helper library named deviceTools.js was added to make it slightly easier to write a raw device. In the longer run (see #1346), we'd like these devices to support Promises and plain object references. This change doesn't go that far. The remaining limitations are: * the deviceTools.js library refuses to handle exported objects, imported foreign device nodes, or promises of any sort * the outbound translator (deviceKeeper.js `mapDeviceSlotToKernelSlot`) refuses to handle exported objects and exported promises * the vat outbound translator (vatTranslator.js `translateCallNow`) refuses to handle promises * liveslots rejects promises in `D()` arguments refs #1346
"Raw devices" bypass the deviceSlots layer and allow device code direct access to `syscall`, and the arguments arriving through the `dispatch` object it must produce. This makes some patterns much easier to implement, such as producing new device nodes as part of the device's API (e.g. one device node per code bundle). It also provides vatstoreGet/Set/Delete, so the device code can manage one piece of state at a time, instead of doing an expensive read-modify-write cycle on a single large aggregate state object. A helper library named deviceTools.js was added to make it slightly easier to write a raw device. In the longer run (see #1346), we'd like these devices to support Promises and plain object references. This change doesn't go that far. The remaining limitations are: * the deviceTools.js library refuses to handle exported objects, imported foreign device nodes, or promises of any sort * the outbound translator (deviceKeeper.js `mapDeviceSlotToKernelSlot`) refuses to handle exported objects and exported promises * the vat outbound translator (vatTranslator.js `translateCallNow`) refuses to handle promises * liveslots rejects promises in `D()` arguments refs #1346
#4419 has landed, providing all the functionality I care about right now. The new The remainder of this ticket is:
None of those are mainnet-1 -critical, so I'm moving this ticket out of the MN-1 stack. If we don't make those changes before MN-1 launch, I don't know how we'd migrate to the new devices later, but I think we can survive on the existing devices without problems (although #4282 and #4286 may requires changes to the timer device, which might be an excuse to do the work earlier). |
Ugh, this got moved back to the MN-1 pipeline because the PR finally landed. I'll move it back out. |
What is the Problem Being Solved?
While walking @FUDCo through #1339, we were discussing the limitations of swingset Devices which prompted @Chris-Hibbert and I to add a companion vat to each device:
getDeviceState()
andsetDeviceState(newState)
at the right times. These go throughmarshal()
so devices can include Presences in their state.sendOnly
D(timer).wakeUpAt(when).then(makeCoffee)
)sendOnly
to the companion vat, and returns. Later, when the vat receives that message, it resolves the promise for the original callerI'm starting to think that some of the problem comes from trying too hard to retain the liveSlots model in the device world. We have a stripped-down version of liveSlots named
deviceSlots.js
for devices, which lets them use Presences in the same way as vats do (although usingSO(target).method(args)
instead ofE()
, to reflect the send-only nature). But because devices can't use orthogonal persistence, devices can't persist the objects they export (#62), so they can't really provide objects as part of their API. And Promises were already removed, so those are off the list too.So now I'm thinking that it might be better to go low-tech. If we removed
deviceSlots
, and defined devices in terms of the low-levelsyscall
anddispatch
objects, then we could bring back the full API surface (imported/exported objects, imported/exported promises), which would let us have comfortable device APIs without the awkward companion vat.Description of the Design
Devices written in this style would be pretty raw. There would be no
Promise
objects and noE()
orSO()
wrappers to send messages. Device code would handle object/promise/device-node reference identifiers directly (strings likeo+123
), which means there would be no ocap safety within a device (any device code that can send a message to one remote object could equally easily send it to any remote object that's been shared with the device).The timer device in this world might look something like this:
(this presumes a change to the way we set up devices, to return an
externals
table, but now I'm thinking that might be a nice way to help #720 : acontroller.addDevice(bundleFile, endowments)
call would return thisexternals
object to the caller, wrapped in the #720 queue handler, instead of having the host call a{externals, bundleFile, deviceEndowments} = buildFooDevice(endowments)
function ahead of time, keeping 'externals' for itself and passing the rest into the kernel)To add the ability to cancel alarms, we'd want to return an object from
setWakeup
:I can imagine some helper functions that provide affordances for mapping the target to a given handler, in which the handlers are populated at device startup time (by parsing
state.get
) or later as new objecst are created, to make this process less manual. I'd also add helpers for returning a record that contains slots (a subset of whatmarshal
does, written in terms of slotIDs instead of Presence and Promise objects).Security Considerations
The loss of intra-device authority limits is a bit sad, as it reverts the device security model to the "bad old days" where a bug anywhere in the file would compromise the entire device. But I'm not sure we're benefiting from that model in
deviceSlot
-based devices anyway: devices are pretty small and there isn't a lot of internal partitioning anyways (and the availability ofsetDeviceState
everywhere is a pretty broad compromise vector).This approach makes it harder to make sure we're answering every message: there's no implicit
return undefined
at the end to cause asyscall.fulfillToData()
on each invocation ofdispatch.deliver
. Forgetting that would make callers usingawait
to hang, and would prevent the result promises from ever getting GCed.I think the overall tradeoff is:
This would obsolete #1316, since we'd be deleting
deviceSlots.js
, and removing Presence objects from the device environment.The text was updated successfully, but these errors were encountered: