Skip to content

refactor(extension): Extract kernel and vat logic into new package - #79

Merged
rekmarks merged 19 commits into
mainfrom
sirtimid/kernel-vat-abstraction
Sep 23, 2024
Merged

refactor(extension): Extract kernel and vat logic into new package#79
rekmarks merged 19 commits into
mainfrom
sirtimid/kernel-vat-abstraction

Conversation

@sirtimid

@sirtimidsirtimid commented Sep 19, 2024

Copy link
Copy Markdown
Member

Closes#56

Extracts kernel and vat logic from the extension to a new package, @ocap/kernel.

} from './types.js';
export { KernelMessageTarget, Command } from './types.js';
export {
makeMessagePortStreamPair,

@sirtimidsirtimidSep 19, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Here I removed from the exports many unused methods and types, we should check if we still need everything in this package and then we should clean up a bit

@sirtimid
sirtimid marked this pull request as ready for review September 19, 2024 20:27
@sirtimid
sirtimid requested a review from a team as a code ownerSeptember 19, 2024 20:27

@rekmarksrekmarks left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A pleasing refactor! See comments.

Comment on lines +5 to +10
export enum KernelMessageTarget {
Background = 'background',
Offscreen = 'offscreen',
WebWorker = 'webWorker',
Node = 'node',
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The original ExtensionMessageTarget enum was intended specifically for the chrome.runtime.sendMessage() API. If we want an enum like KernelMessageTarget, it should reference things like vat, kernel, and console, not web extension components.

This problem is to some extent preexisting, because we're mixing different abstraction layers. Ideally, our notion of kernel commands, for example, shouldn't have any knowledge of platform implementation details.

After further consideration, I think we should just commit to creating an issue here, and address this in a follow-up. I'm tempted to take a stab at establishing a cleaner abstraction boundary.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I ll leave that for the next iteration then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's not just that the contents of the enum is specific to its use with the chrome.runtime.sendMessage API, but the enum's very existence is an artifact of how that API works. Its message targeting scheme is not only gratuitously different from the other web message APIs (e.g., MessagePort.postMessage) but also very anti-ocap'y. It or something like it is arguably necessary as an under-the-hood implementation artifact, but I don't think it should be generalized the way Eric proposes. It's better to have some kind of object that represents where a message should go, which can be a component of a cleaner message passing API that layers over all the web crap. Ocaps are not just an abstraction for security purposes, they're a pathway to sounder software architecture and safer code.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure that any code that references the specific componentry of our system belongs in the streams package at all, which feels to me like it should be completely generic. However, if the plan is for this enum to evaporate soon I imagine we can live with it being here for now.

Comment threadpackages/extension/src/makeIframeVatRealm.ts Outdated
Comment threadpackages/kernel/.eslintrc.cjs Outdated
Comment threadpackages/kernel/package.json
Comment threadpackages/kernel/tsconfig.json Outdated
Comment threadpackages/kernel/tsconfig.json Outdated
Comment threadpackages/kernel/tsconfig.json Outdated
Comment threadpackages/kernel/tsconfig.build.json
Comment threadpackages/kernel/src/Kernel.ts Outdated
Comment threadpackages/kernel/src/Vat.ts Outdated

@grypezgrypez left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Overall I'm a big kernel fan. I have a suggestion for how to remove a few ?s.

Comment on lines +23 to +33
/**
* Adds a vat to the kernel.
*
* @param vat - The vat record.
*/
public addVat(vat: Vat): void {
if (this.#vats.has(vat.id)) {
throw new Error(`Vat with ID ${vat.id} already exists.`);
}
this.#vats.set(vat.id, vat);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we imagine the kernel receiving vats it didn't create itself?

We have LaunchVat in the design doc, which can save us some async construction defensiveness by accepting the VatWorker, awaiting it, and passing the resolved stream to the Vat.

Suggested change
/**
*Addsavattothekernel.
*
* @paramvat-Thevatrecord.
*/
publicaddVat(vat: Vat): void{
if(this.#vats.has(vat.id)){
thrownewError(`Vat with ID ${vat.id} already exists.`);
}
this.#vats.set(vat.id,vat);
}
/**
*Launchesanewvat.
* @paramvat-Thevatrecord.
*/
publiclaunchVat({ id, worker }: Omit<VatProps,'streams'>): Promise<Vat>{
if(this.#vats.has(vat.id)){
thrownewError(`Vat with ID ${vat.id} already exists.`);
}
const[streams,]=awaitworker.init(id);
constvat=newVat({ id, worker, streams });
this.#vats.set(vat.id,vat);
awaitvat.init();
returnvat;
}

Possibly it is reasonable to withhold the worker from the vat entirely, and instead invoke the worker's delete method from the Kernel.

@sirtimidsirtimidSep 20, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I like this approach, I m going to implement it, but I will pass the delete method in the vat I don't want to store it in the kernel

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think I like launchVat() (startVat()?), but curious to hear what the author has to say.

I do think it's a good idea to withhold the worker from the vat, and manage its lifecycle / bookkeeping outside of the vat. The vat doesn't need to know where or how it came into being, and the kernel has to directly manage its lifecycle (and implicitly the lifecycle of its worker) anyway.

Food for future thought: Ultimately, I think the kernel should be responsible for launching / starting a vat, and the vat object should be constructed in that method. However, I don't think that a worker should be passed to the kernel method responsible for this. Rather, we should establish a notion of a "worker service" that is passed to the kernel on construction time, from which it can request workers. The reason being: only the kernel can determine when it needs a new vat, at which point it needs to ask for a worker. We have an abstraction boundary similar to this in Snaps, and it's served us well there.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Ok let's do this one step at a time, first I will implement @grypez suggestion passing the worker on launchVat. The worker service can be done on another PR

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

@rekmarks@grypez I made the change

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It can make total sense for a vat to be able to launch another vat, as it can be very useful in some applications, but it must do so via a capability (which the typical vat would not be given) that lets it request the kernel to start the second vat on its behalf, rather than by somehow creating the second vat directly. In other words, the kernel mediates all vat creation. The same logic applies to vat destruction.

Erik's remark:

The vat doesn't need to know where or how it came into being, and the kernel has to directly manage its lifecycle (and implicitly the lifecycle of its worker) anyway

Is spot on.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we should establish a notion of a "worker service" that is passed to the kernel on construction time, from which it can request workers

SwingSet has something called the vatLoader that abstracts away the low level mechanics of creating and launching a vat, with a sort of plug-in architecture for the actual implementation of each of the different ways this can happen. In SwingSet, there are three supported cases: an external NodeJS worker process, an external XSnap (XS) worker process, and running the vat locally within the kernel process itself. Production operations use the xs-worker and local vats (the latter for certain special inside-the-TCB vats such as comms), while the node-worker and local vats (used more generally) are very useful for debugging. The thing Erik describes sounds a lot like that. I suspect our thing will let us choose whether the vat is in a web worker or an iframe, and whether to run it natively or in xsnap wasm or something like that (I could imagine getting really fancy and having something like a special wasm stub that lets us launch an actual external OS process and run in that instead of running in the browser at all, but that seems highly speculative).

Comment threadpackages/kernel/src/Kernel.ts
Comment threadpackages/streams/src/type-guards.test.ts Outdated

@FUDCoFUDCo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Big refactorings always tempt me towards infinitely subdividing fractal quibbles, but we'll get to those in due time. I call this a win.

Huge progress towards reshaping things into the way they ought to be. Bravo!

Comment on lines +5 to +10
export enum KernelMessageTarget {
Background = 'background',
Offscreen = 'offscreen',
WebWorker = 'webWorker',
Node = 'node',
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure that any code that references the specific componentry of our system belongs in the streams package at all, which feels to me like it should be completely generic. However, if the plan is for this enum to evaporate soon I imagine we can live with it being here for now.

Comment on lines +40 to +51
type CommandMessage<TargetType extends KernelMessageTarget> =
| CommandLike<Command.Ping, null | 'pong', TargetType>
| CommandLike<Command.Evaluate, string, TargetType>
| CommandLike<Command.CapTpInit, null, TargetType>
| CommandLike<Command.CapTpCall, CapTpPayload, TargetType>;

export type ExtensionMessage = CommandMessage<ExtensionMessageTarget>;
export type IframeMessage = CommandMessage<never>;
export type KernelMessage = CommandMessage<KernelMessageTarget>;
export type VatMessage = CommandMessage<never>;

export type WrappedIframeMessage = {
export type WrappedVatMessage = {
id: MessageId;
message: IframeMessage;
message: VatMessage;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Similar to my comment above, I really don't think representations of specific message protocol elements belong in the streams package.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I've left all these types in place, either in anticipation of upcoming changes or with the expectation that @rekmarks will update them once this PR is merged, as he indicated.

@grypezgrypez left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A few more changes relevant to this issue's scope, but overall I like where the PR is at.

Comment threadpackages/kernel/src/Kernel.ts Outdated
Comment threadpackages/kernel/src/Kernel.ts Outdated
Comment threadpackages/kernel/src/Kernel.test.ts Outdated
Comment threadpackages/kernel/src/Vat.ts Outdated
Comment threadpackages/kernel/src/Vat.ts Outdated
grypez
grypez previously requested changes Sep 23, 2024

@grypezgrypez left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Only nits.

Comment threadpackages/kernel/src/Vat.test.ts Outdated
Comment threadpackages/kernel/src/Vat.test.ts Outdated
Comment threadpackages/kernel/src/Kernel.test.ts Outdated
Comment threadpackages/kernel/src/Kernel.test.ts Outdated
@grypez

Copy link
Copy Markdown
Member

LGTM, pending others' reviews.

@grypez
grypez dismissed their stale reviewSeptember 23, 2024 17:46

Changes addressed

@sirtimidsirtimid changed the title feat(WIP): Add kernel packagefeat: Add kernel packageSep 23, 2024

@rekmarksrekmarks left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just a couple of nits.

Comment threadpackages/test-utils/src/makeCapTpMock.ts Outdated
Comment threadpackages/test-utils/src/makeCapTpMock.ts Outdated
Comment threadpackages/streams/tsconfig.json Outdated
Comment on lines +56 to +58
await vat.terminate();
await worker.delete();
this.#vats.delete(id);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For future reference: we should consider what should happen when either of these promises reject, and whether to delete the vat from #vats anyway.

@rekmarksrekmarks changed the title feat: Add kernel packagerefactor(extension): Extract kernel and vat logic into new packageSep 23, 2024
@rekmarks
rekmarks enabled auto-merge (squash) September 23, 2024 21:23
@rekmarks
rekmarks merged commit 0c52998 into mainSep 23, 2024
@rekmarks
rekmarks deleted the sirtimid/kernel-vat-abstraction branch September 23, 2024 21:23
rekmarks added a commit that referenced this pull request Sep 24, 2024
The command / message types have frustrated us on various occasions,
e.g. [here](#79 (comment))).
This is in part because they blur abstraction boundaries between the
kernel and the platform it runs on. Here we attempt to delineate this
abstraction boundary by extracting these types into a new "kitchen
drawer" package (`@ocap/utils`), and refactoring them such that only a
smaller set needs to be shared between different packages. In addition,
the `Command`-family of types and enums receives an overhaul.
Changes in detail:
- Extracts use case-specific types and utilities from `@ocap/streams`
into a new package, `@ocap/utils`
- Extract single-package types into their respective packages from
`@ocap/utils`
- Rename command types, see `utils/src/types.ts`
- The `Command` type itself blurs our ideal abstraction boundary by
implementing low-level plumbing like `ping` alongside porcelain such as
`callCapTp. Fixing this is deferred to future work.
- Notably, the string enum of command names is now named
`CommandMethod`, and the actual command payload `Command`. Various
unnecessary generic parameters have been removed.
---------
Co-authored-by: grypez <143971198+grypez@users.noreply.github.com>
Sign up for freeto 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.

refactor(extension,streams): Abstract vat logic from platform-specific implementation details

4 participants

@sirtimid@grypez@FUDCo@rekmarks