-
Notifications
You must be signed in to change notification settings - Fork 24
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
Discussion: More immutability in webnative #226
Comments
Yeah, this is totally a "measure twice / cut once". It should probably also happen at the same time as the general FS spec revisions. I've been doing some poking around. You're going to laugh when you see the state of the art: "In this paper we use optimistic concurrency techniques adapted from software transactional memory to develop a concurrent tree data structure." Soooo exactly what we were saying on the call 😆 Except that we're implementing something STM-like, so that doesn't really help us too much.
Yeah, exactly. There's a few edge cases, but in theory a light reference index for the latest version feels like the right direction.
That would be ideal. Let's get this fully specced out, and then see which bit can be done first 👍 Okasaki was surprisingly unhelpful on concurrent inserts, aside from the classic merge operation which doesn't totally work for use because we have namespace contention. I have some papers open; will share any findings! |
Working thoughts on approaches: Switching to a less mutable solution makes keeping track of various parts of the state much more straightforward, and we get to align with the immutability inherent in content addressing. Linearization (Fully Immutable)
Optimistic Fork/Merge (Fully Immutable)A variant on linearization. This feels more sophisticated.
Rough outline:
Global Rebuilding (Fully Immutable)
Immutable Data / Mutable Spine (Mostly Immutable)Need to circle back to this; it's late and I'm forgetting why we liked this earlier 😛 The multiple hours of unrelated tasks between when Philipp and I discussed this and now probably doesn't help 😅 🧠
Links |
I find the distinction between fully and mostly immutable confusing. If the surface level API is supposed to look like this: const permissions = ...
const { fs } = await webnative.initialise({ permissions })
fs.write("private/a.txt", "sth")
fs.write("private/b.txt", "sth") Then we will always have to have a layer of mutability in there. The only way we can ever be fully immutable is when we expose an API like this: const permissions = ...
const { fs } = await webnative.initialise({ permissions })
const fs2 = fs.write("private/a.txt", "sth")
const fs3 = fs2.write("private/b.txt", "sth") (At least that's how I am thinking about this in my head) I must be misunderstanding what you refer to when you/Okazaki says "can be implemented fully immutable". Also, I admit I haven't read the okazaki book. I should really do that :) |
Ok, so I've been chewing on this for a bit. Given that we need some kind of merging operation on filesystems when we implement conflict resolution (rebasing), I think it makes a lot of sense to implement the optimistic fork/merge strategy. This reminds me that this discussion is probably also highly relevant for filesystem transactions in webnative. (isn't a transaction basically a fork & merge?) |
It is! This is fully implementation detail, and a question of if any part can update their fields, or if you're only constructing new data structures with pointers back into old data. A surface level API is a totally separate concern; we can do that with any of these techniques underneath |
They're closely related! The atomic transactions are higher granularity than this: literally toss the existing tree, do a rebase (with the same merge) and replay the anonymous function(s). Can we make this one mechanism? Probably! Needs a bit more thought, though. |
(also FWIW I also have a gut reaction in favour of the fork/merge option) |
Random thought while in an unrelated call, so just a quick sketch of a stray idea: In fork/merge, if we're doing associative merges with history, we don't want to have to walk back in the linked list to fix all previous references when there's no contention. It may make sense to do this with CPS / manual laziness while in the array, to make merging to an arbitrary spot more performant (where the continuation is the part pointing back at unknown history). |
(Came up in a call with @expede)
Recently a couple of concurrency issues surfaced. They stem from data races, so the root problem in these cases is that some mutable state is modified by multiple entities.
We should think about making more of our data structures immutable.
Concretely, this would mean e.g. making the
MMPT
class only have immutable members, and e.g. itsadd()
method return a new reference to a new MMPT.We also have data races - as the
concurrency.test.ts
sometimes reveals - in the private file system code in (I think)PrivateTree
(I did some testing in thematheus23/fix-concurrency
).We should think about also making this data structure immutable.
This is obviously a bigger refactor, so we should plan this a little better. Maybe we do this incrementally?
Also, at some point we have to put a mutable API over the immutable code, as that's more in line of how e.g. an app developer thinks about data storage (duh!).
We think we might want to have a very thin "mutability" layer, think of it like an index into the individual, immutable private file system trees
I'm thinking of also exposing the immutable file system stuff, so it's still composable, because once you create a "mutable" layer on top of an immutable API, you'll never truly get back the immutable API. I don't want to lose the immutable API in case anyone wants to integrate it into an immutable-first codebase.
The text was updated successfully, but these errors were encountered: