Skip to content

Conversation

@grypez
Copy link
Contributor

@grypez grypez commented Oct 16, 2024

Closes: #99 #170

Changes

@ocap/extension

  • Updates VatWorkerServer and VatWorkerClient to implement the new terminateAll method.

@ocap/kernel

  • Renames initWorker resp. deleteWorker vat worker service methods to launch resp. terminate.
  • Adds a new terminateAll method to the vat worker service interface.
  • Adopts @ocap/errors for VatAlreadyExistsError and VatDeletedError.
  • Implements a generic IdentifiedMessageKit in messages/message-kit.ts which abstracts the { id, payload } form used by both VatCommand and VatWorkerServiceCommand to handle messages by id rather than method.

Comments

It would also be nice to use either the PostMessageStream or MessagePortStream from @ocap/streams instead of the ad hoc approach I have taken here; but because the transfer argument is a peer to, not a child of, message, some workaround is necessary at the interface level and the type guard level to permit this use case.

@grypez grypez force-pushed the grypez/vws-terminate-all branch 4 times, most recently from 29c8e4e to 4f24659 Compare October 18, 2024 04:26
@grypez grypez force-pushed the grypez/vws-terminate-all branch from 4f24659 to 2dbded0 Compare October 18, 2024 17:52
@grypez grypez marked this pull request as ready for review October 18, 2024 18:05
@grypez grypez requested a review from a team as a code owner October 18, 2024 18:05
const vatMessageKit = makeMessageKit(vatCommand);
const vatMessageKit = makeIdentifiedMessageKit(
vatCommand,
(value: unknown): value is `${VatId}:${number}` => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on the other hand we could extract this to a isValidVatCommandId function to show intent better

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the id guard is only used internally to the kit I do not see the need to give it a name.

I could have makeIdentifiedMessageKit accept an object, so that this call would look like:

const vatMessageKit = makeIdentifiedMessageKit({
  source: vatCommand,
  isMessageId: (value: unknown): value is `${VatId}:${number}` =>
    typeof value === 'string' &&
    /^\w+:\d+$/u.test(value) &&
    isVatId(value.split(':')[0]),
});

Would that suffice?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 8c3f217

Copy link
Member

@rekmarks rekmarks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First, a word of caution:

Naively, given a method foo, implementing fooAll should amount to something like:

  • Add a field fooMap: Map<FooId, Foo> = new Map();
  • for (const id of fooMap) { this.terminate(id); }

That we require a PR of this magnitude to accomplish the above is a sign that our abstractions are too numerous and don't have the right responsibilities. Not much to do here, but something to keep in mind.

Second, I think we can potentially make a couple of modifications to our approach here:

Partially adopts @ocap/errors standards for VatAlreadyExistsError and VatDeletedError.

I believe this refers to where our well-known error codes occur as strings. A partial adoption of something is a potential source of bugs and confusion, so we may want to just give up on this until #170 can be addressed.

Implements a generic IdentifiedMessageKit in messages/message-kit.ts which abstracts the { id, payload } form used by both VatCommand and VatWorkerServiceCommand to handle messages by id rather than method.

Per @sirtimid's comment, I'm not sure that this is different enough to warrant its own abstraction.

Comment on lines 33 to 46
describe('isVatWorkerServiceCommand', () => {
describe.each`
payload
${launch}
${terminate}
`('$payload.method', ({ payload }) => {
it.each(sharedCases(payload))(
'returns %j for %j',
(expectedResult, _, value) => {
expect(isVatWorkerServiceCommand(value)).toBe(expectedResult);
},
);
});
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is terminateAll not tested here?

Copy link
Contributor Author

@grypez grypez Oct 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should have been. a60ec03

Comment on lines 14 to 24
export const hasMarshaledError = <Mode extends 'required' | 'optional'>(
mode: Mode,
value: object,
...codes: ErrorCode[]
): value is Mode extends 'required'
? { error: MarshaledError }
: { error?: MarshaledError } =>
(mode === 'optional' && !hasProperty(value, 'error')) ||
(isObject(value) &&
typeof value.error === 'string' &&
codes.some((code) => (value.error as string).startsWith(code)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know you could do conditional type guards, and now that I do, I'm not sure how I feel about it.

This particular one has a number of issues.

In the first place, its highly specialized nature means that it should probably make its home in vat-worker-service.ts.

Second, it is probably better to split it into two separate functions, one for each "mode". However, it may be possible to elide the the "optional" mode completely, since we can assert that an arbitrary object has optional properties without type guards.

Third, in its actual usage, we seem to be looking for specific error codes, but the actual type we get out of the deal doesn't retain this information. Generally, we shouldn't have to care about which specific error is attached to some object; if it's an error code we know, we know how to handle it. Ensuring that the correct errors are thrown is the responsibility of the unit tests of the code that throws those errors, not a concern of our messaging layer.

In sum, we can probably just let the recipient figure out the kind of error that it is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the first place, its highly specialized nature means that it should probably make its home in vat-worker-service.ts.

Okay 👍

Second, it is probably better to split it into two separate functions, one for each "mode".

I don't mind doing this, but, given that the split results in more and duplicated code, and the usage

hasMarshaledError('optional', reply, ErrorCode.VatAlreadyExists)

has comparable verbosity and intellisense support to

hasOptionalMarshaledError(reply, ErrorCode.VatAlreadyExists)

I would like to know why you think it is probably better to split up.

However, it may be possible to elide the the "optional" mode completely, since we can assert that an arbitrary object has optional properties without type guards.

The optional type guard asserts that if the value exists then it has the right type. This is not equivalent to not checking, and is the correct way to check for a ? typed property.

Third, in its actual usage, we seem to be looking for specific error codes, but the actual type we get out of the deal doesn't retain this information.

I support your concern for the misalignment of the guarded and declared type. I note that since the guard implementation is narrower than its guarded type the resulting code should remain type safe under our usage conventions if (!isX(value)) { throw new Error } and if (isX(value)) { useX(value) } which I understand to be in place to support this kind of weak assertion.

Generally, we shouldn't have to care about which specific error is attached to some object; if it's an error code we know, we know how to handle it. Ensuring that the correct errors are thrown is the responsibility of the unit tests of the code that throws those errors, not a concern of our messaging layer.

This convention is already in use in @metamask/snaps so I am not keen to argue against it for very long. That said,

I have typically conceived of errors as a possible return type of the throwing routine, and therefore they would be a valid concern of the message types that transmit return values. I think 'if we know the error code, we know how to handle it' introduces implicit dependency between e.g. client and server which would be explicit if the error codes were included in the message reply types. A simple example of that implicit dependency going awry is a client that devotes code to handling error messages the server never sends.

The presence at all of the messaging layer in this case arises as an artifact of the extension implementation; a node implementation probably doesn't need it at all. I think this indicates the message types here should be declared in @ocap/extension, however that requires me to export some things I don't wish to export.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the marshaling working the point is moot, check b063c07

@grypez grypez force-pushed the grypez/vws-terminate-all branch from 2dbded0 to b5430a5 Compare October 22, 2024 21:37
@grypez grypez requested review from rekmarks and sirtimid October 22, 2024 22:13
@rekmarks
Copy link
Member

@grypez I'm getting a lot of type errors locally in the vat worker service tests files. Are they there for you?

See also: #164 (comment)

@sirtimid
Copy link
Contributor

sirtimid commented Oct 24, 2024

@grypez I'm getting a lot of type errors locally in the vat worker service tests files. Are they there for you?

See also: #164 (comment)

@grypez @rekmarks The new yarn test:ts command shows them as well

Screenshot 2024-10-24 at 16 34 23

@grypez
Copy link
Contributor Author

grypez commented Oct 24, 2024

Types are once again quasi-coherent 7b67fa8. Note the one as unknown as is guarded by a runtime assertion.

@grypez grypez enabled auto-merge (squash) October 24, 2024 18:41
Copy link
Member

@rekmarks rekmarks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@grypez grypez merged commit b0b2ab7 into main Oct 24, 2024
@grypez grypez deleted the grypez/vws-terminate-all branch October 24, 2024 19:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement "vat loader"

4 participants