-
Notifications
You must be signed in to change notification settings - Fork 6
Implement kernel storage abstraction layer #180
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
Conversation
2c3fb23 to
f9dd5b4
Compare
| * @returns The value at that key. | ||
| */ | ||
| function kvGet(key: string): string { | ||
| function kvGet(key: string, required: boolean): string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| function kvGet(key: string, required: boolean): string { | |
| function kvGet(key: string, required: boolean): string | undefined { |
But why don't we want to return undefined? I don't get that return undefined as unknown as string; below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naively, I agree it seems preferable for this function to be honest about occasionally returning undefined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is where TypeScript and I butt heads a lot. Although adding undefined to the possible return types is strictly more correct, "might be undefined" introduces a type contagion that infects distant parts of the call graph, even though in all but a few cases undefined is never an actual possibility and the declaration forces everything that ever touches the return value to implement code to account for the thing that will never happen happening just to make the compiler happy (plus of course all those conditionals cluttering up the code will show up as gaps in code coverage since the thing never happens).
In practice I've found it's sometimes cleaner to pretend to the type system that it's always just a string (or whatever), then just explicitly look for undefined in the few places where that's actually relevant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's not really possible to retrieve an undefined key in practice, why don't we just get rid of getRequired and make get throw if the key is undefined? At the moment, should get ever return undefined, we will get a bunch of hard-to-decipher errors, when we could just get an easily comprehensible one instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is possible to retrieve an undefined key in practice, and in fact it's useful for testing if a value has been set in the first place (particularly useful in the case of lazy initialization of things that might never be used), so merely having the result be undefined is not itself an error condition. It's only an error when you need the value to be there. In a nutshell, the problem is that TS wants you make an a priori policy decision as to whether undefined is always ok or never ok, but doesn't deal gracefully with the common case where ok vs. not ok is context sensitive.
|
So if I'm getting this right, based on the description, we can create a couple of separate followup issues
What about these:
|
rekmarks
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! I have mostly questions and minor suggestions.
Speaking of which, what exactly is a "kernel promise"? Where do they come from? What are they for?
| * @returns The value at that key. | ||
| */ | ||
| function kvGet(key: string): string { | ||
| function kvGet(key: string, required: boolean): string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naively, I agree it seems preferable for this function to be honest about occasionally returning undefined.
| * @param str - The string. | ||
| * @returns The same string coerced to type Message | ||
| */ | ||
| function sm(str: string): Message { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| function sm(str: string): Message { | |
| function asMessage(str: string): Message { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, I think I'd prefer the creation of a makeMessage factory function so we don't have to lie. Not a blocker because we have not sworn an oath of honesty to TypeScript.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was intended as a piece of syntactic sugar for the sole purpose of getting TS to shut up, so I was aiming to make it as close to invisible as possible. I actually wanted to use a one character name but our lint rules wouldn't allow for that. Having a genuine Message factory function might be better once we're actually in a position to have more definite opinions about what a Message really is, but here all that was important was that the thing be stringifiable and pass the type checker.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I don't mind the existence of the function, nor do I mind abbreviations that I can decipher, but I'm a strong proponent of self-documenting names, and I can't make heads or tails of sm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if it would be useful to have a conventional terse name (e.g., x) that means "function that's purely instrumental for making TypeScript shut up; just pretend it's not even here". 😃
(Note that the major and probably only use of this would be in constructing tests, which often erect rickety and not quite kosher scaffolding to avoid sucking in more of the world.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I revised it to have better documentation and an arguably better name that goes with the documentation, but still terse. Lemme know what you think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha, I yield!
This is the kernel's representation of a promise that has been exported from one vat and imported into one or more others. If the vat(s) into which it has been imported sends a message to it, the message has to wait somewhere for the promise to be resolved, plus when the promise settles the requisite notifications have to be sent to any vats that might be doing a |
If we decide to go the SQL route, for a lot of the data I'd want implement much of the functionality in terms of more direct database operations rather than having to map everything onto a dumb key/value store. So I think a priority is probably making that decision (which may unpack into some additional benchmarking if we don't already know enough to decide (which we might)). Caching is another interesting question. Right now it caches specific items which we know are going to be used with high frequency, rather that doing some kind of heuristic general cache thing for arbitrary data, but we might well want to add such a thing. One thing I've never been able to get a clear read on is how much, if any, caching SQLite itself already does internally -- I can imagine anything from none at all to speak of to quite a lot. |
|
The newly introduced command
ps: I am renaming it to |
Not directly germane to the issue you're flagging, but: I'm really not a fan of using namespaced labels (e.g., |
|
@FUDCo I agree, and as of #197 Lines 23 to 29 in 466a4c4
|
a1992ed to
8e84a92
Compare
rekmarks
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!

This is a first cut at a storage wrapper for the kernel. What I have assumes it is passed a dumb string/string key/value store, and builds on top of that, sometimes in ways that are necessarily a bit indirect. Obviously, if we have a more featureful storage substrate we could potentially take advantage of its features to do this stuff more directly (such a thing would also imply refactoring the construction API to push the responsibility for creating whatever object or objects actually accesses the storage to a lower level in the code, rather than simply passing in the kv store at the top).
It's also worth mentioning that at this point there is not yet any notion of transaction boundaries, which will absolutely be needed but which is more wrapped up in the specifics of the underlying storage layer than the stuff here is.