-
Notifications
You must be signed in to change notification settings - Fork 205
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
match incref/decref calls within kernel #3264
Comments
cc @FUDCo |
For messages, here's one approach:
I like the idea of strictly managing the refcounts from within One downside is the DB churn when we pull an item off the run-queue only to push it onto a promise-queue, and likewise when promise-queue messages are transferred right back to the run-queue. If/when we implement promise forwarding (resolving a promise to a different promise), we might do more of the same, depending upon the implementation and the ordering properties we choose to provide. So an alternative approach would avoid the churn when moving messages, and instead aim to incref/decref only upon the creation or destruction of messages:
This would result in less down-then-up refcount churn, but would distribute the incref/decref calls out through more code, which feels less reliable to me. |
@FUDCo had a great idea: add a method to |
Add more type assertions to kernelKeeper.addMessageToPromiseQueue(), which is defined to take a Message (with method, result, and args capdata). test-state was giving it the wrong things (run-queue events, which include both 'send' for messages, and 'notify' for promise resolution notifications). Promise queue entries are only ever message sends, never anything else. I clean up test-state to only queue messages, and to track a few more values that will be meaningful when the refcounts and queue handling change soon. I also changed the resolution tests to use properly-allocated objects, rather than arbitrarily-chosen object references. This doesn't matter now, but when the upcoming refcount improvements are made, `kernelKeeper.resolveKernelPromise` will need the capdata to reference real objects with real refcounts that can be modified. refs #3264 (prep/cleanup)
As described in #3264, we were not maintaining correct reference counts when messages get queued to an unresolved promise. In the new approach, the lifetime of a message is: * all krefs (target, args, and optional result) of a message are increfed during `doSend`, as before * this adds a `type: 'send'` event to the run-queue * when the `send` event is pulled off the run-queue, all krefs are decrefed (as before) * if the event is delivered to a vat, great * if the event is delivered to a promise, `kernelKeeper.addMessageToPromiseQueue` increfs all krefs * (this is new: previously `addMessageToPromiseQueue` did not change the refcounts) * later, if/when the promise is resolved, the messages are transferred laterally from the promise queue to `send` events on the run-queue, without changing their refcounts * (this is new: previously the transfer was done with `doSend`, which incremented the refcounts) * the counts are decremented when the `send` event is removed from the run-queue, as usual The result is the same number of increments and decrements as before, but the increment is done earlier, so messages on a promise queue will maintain a refcount on all their krefs (target, args, and any result). This protects objects and promises which would otherwise have been eligible for collection while they sat on the promise queue. Strictly speaking we don't need to maintain a refcount on the target (which is also kind of redundant: everything on the queue for promise `kp1` obviously has `target: 'kp1'`), since that will always be additionally held by the c-list of the decider (at least). But by making all promise queues do the same thing as the main run-queue, we can laterally transfer messages from promise- to run-queue without DB churn (decrementing then immediately incrementing the counts). This change moves the responsibility for the lateral transfer from `notifySubscribersAndQueue` in kernel.js to `kernelKeeper.resolveKernelPromise()`, deleting the former entirely and merging the subscription loop into `doResolve`. closes #3264
As described in #3264, we were not maintaining correct reference counts when messages get queued to an unresolved promise. In the new approach, the lifetime of a message is: * all krefs (target, args, and optional result) of a message are increfed during `doSend`, as before * this adds a `type: 'send'` event to the run-queue * when the `send` event is pulled off the run-queue, all krefs are decrefed (as before) * if the event is delivered to a vat, great * if the event is delivered to a promise, `kernelKeeper.addMessageToPromiseQueue` increfs all krefs * (this is new: previously `addMessageToPromiseQueue` did not change the refcounts) * later, if/when the promise is resolved, the messages are transferred laterally from the promise queue to `send` events on the run-queue, without changing their refcounts * (this is new: previously the transfer was done with `doSend`, which incremented the refcounts) * the counts are decremented when the `send` event is removed from the run-queue, as usual The result is the same number of increments and decrements as before, but the increment is done earlier, so messages on a promise queue will maintain a refcount on all their krefs (target, args, and any result). This protects objects and promises which would otherwise have been eligible for collection while they sat on the promise queue. Strictly speaking we don't need to maintain a refcount on the target (which is also kind of redundant: everything on the queue for promise `kp1` obviously has `target: 'kp1'`), since that will always be additionally held by the c-list of the decider (at least). But by making all promise queues do the same thing as the main run-queue, we can laterally transfer messages from promise- to run-queue without DB churn (decrementing then immediately incrementing the counts). This change moves the responsibility for the lateral transfer from `notifySubscribersAndQueue` in kernel.js to `kernelKeeper.resolveKernelPromise()`, deleting the former entirely and merging the subscription loop into `doResolve`. closes #3264
As described in #3264, we were not maintaining correct reference counts when messages get queued to an unresolved promise. In the new approach, the lifetime of a message is: * all krefs (target, args, and optional result) of a message are increfed during `doSend`, as before * this adds a `type: 'send'` event to the run-queue * when the `send` event is pulled off the run-queue, all krefs are decrefed (as before) * if the event is delivered to a vat, great * if the event is delivered to a promise, `kernelKeeper.addMessageToPromiseQueue` increfs all krefs * (this is new: previously `addMessageToPromiseQueue` did not change the refcounts) * later, if/when the promise is resolved, the messages are transferred laterally from the promise queue to `send` events on the run-queue, without changing their refcounts * (this is new: previously the transfer was done with `doSend`, which incremented the refcounts) * the counts are decremented when the `send` event is removed from the run-queue, as usual The result is the same number of increments and decrements as before, but the increment is done earlier, so messages on a promise queue will maintain a refcount on all their krefs (target, args, and any result). This protects objects and promises which would otherwise have been eligible for collection while they sat on the promise queue. Strictly speaking we don't need to maintain a refcount on the target (which is also kind of redundant: everything on the queue for promise `kp1` obviously has `target: 'kp1'`), since that will always be additionally held by the c-list of the decider (at least). But by making all promise queues do the same thing as the main run-queue, we can laterally transfer messages from promise- to run-queue without DB churn (decrementing then immediately incrementing the counts). This change moves the responsibility for the lateral transfer from `notifySubscribersAndQueue` in kernel.js to `kernelKeeper.resolveKernelPromise()`, deleting the former entirely and merging the subscription loop into `doResolve`. closes #3264
As described in #3264, we were not maintaining correct reference counts when messages get queued to an unresolved promise. In the new approach, the lifetime of a message is: * all krefs (target, args, and optional result) of a message are increfed during `doSend`, as before * this adds a `type: 'send'` event to the run-queue * when the `send` event is pulled off the run-queue, all krefs are decrefed (as before) * if the event is delivered to a vat, great * if the event is delivered to a promise, `kernelKeeper.addMessageToPromiseQueue` increfs all krefs * (this is new: previously `addMessageToPromiseQueue` did not change the refcounts) * later, if/when the promise is resolved, the messages are transferred laterally from the promise queue to `send` events on the run-queue, without changing their refcounts * (this is new: previously the transfer was done with `doSend`, which incremented the refcounts) * the counts are decremented when the `send` event is removed from the run-queue, as usual The result is the same number of increments and decrements as before, but the increment is done earlier, so messages on a promise queue will maintain a refcount on all their krefs (target, args, and any result). This protects objects and promises which would otherwise have been eligible for collection while they sat on the promise queue. Strictly speaking we don't need to maintain a refcount on the target (which is also kind of redundant: everything on the queue for promise `kp1` obviously has `target: 'kp1'`), since that will always be additionally held by the c-list of the decider (at least). But by making all promise queues do the same thing as the main run-queue, we can laterally transfer messages from promise- to run-queue without DB churn (decrementing then immediately incrementing the counts). This change moves the responsibility for the lateral transfer from `notifySubscribersAndQueue` in kernel.js to `kernelKeeper.resolveKernelPromise()`, deleting the former entirely and merging the subscription loop into `doResolve`. closes #3264
Add more type assertions to kernelKeeper.addMessageToPromiseQueue(), which is defined to take a Message (with method, result, and args capdata). test-state was giving it the wrong things (run-queue events, which include both 'send' for messages, and 'notify' for promise resolution notifications). Promise queue entries are only ever message sends, never anything else. I cleaned up test-state to only queue messages, and to track a few more values that will be meaningful when the refcounts and queue handling change soon. I also changed the resolution tests to use properly-allocated objects, rather than arbitrarily-chosen object references. This doesn't matter now, but when the upcoming refcount improvements are made, `kernelKeeper.resolveKernelPromise` will need the capdata to reference real objects with real refcounts that can be modified. refs #3264 (prep/cleanup)
While working through unit tests for #3109, I noticed that we weren't consistently performing matched incref/decref calls in all places within the kernel.
If we trace a typical message, it begines life in the kernel-side handler for
syscall.send
, which routes tokernelSyscall.js: doSend()
:doSend()
before the message is passed tokernelKeeper.addToRunQueue
send
event is pulled off the run-queue bykk.getNextMsg
, the slots are not decrefed at that timeprocessQueueMessage
send
events, the slots are decrefed inprocessQueueMessage
before being sent todeliverToTarget
create-vat
events do not have any slots, so nothing needs to be done for themnotify
events name a promise, however I don't think we need to hold a reference to it (and in fact we probably shouldn't, since it might have been resolved and delivered already, and thus we might have been able to delete it already)deliverToTarget
breaks the target down into several cases:deliverToVat
resolveToError
on their result promise, if anyresolveToError
resolveToError
deliverToVat
kk.addMessageToPromiseQueue
to move the message to the kernel promise table's queue entrykernel.js: doResolve
,kk.getResolveablePromise
returns the complete state of the promise (including the queued messages)kk.resolveKernelPromise
is used to replace the saved state with the resolution datakernel.js: notifySubscribersAndQueue
is called to push anotify
event for each subscriber, and callkernelSyscallHandler.send()
for each queued messagedoSend
, so it increfs the message slots just as if a vat did a brand newsyscall.send
kernelKeeper.js: deleteKernelPromise()
from withinprocessRefcounts()
, which usesdeleteKernelPromiseState()
to delete all the relevant keys. This does not change the refcounts of the resolution data's slots.The first mismatch I'm looking at is a message which gets queued on an unresolved promise. We decref its slots when pulling it off the run-queue, but do not increment them when adding to the promise queue. We do not decref them when removing from the promise queue, but we do incref them when adding the message back to the run-queue. For promises that get resolved, the final refcount is correct, but they'll be too low while the message is on the promise queue, and are thus vulnerable to being dropped early.
The second mismatch is in the promise's resolution data. This is incremented during
doResolve
, and decremented inprocessRefcounts
just before callingdeleteKernelPromise
. I think the incref/decrefs are matched and sound, but it feels dangerous to have the two calls happen in different places: I kind of want both increment and decrement to happen inkernelKeeper.js
.The text was updated successfully, but these errors were encountered: