-
Notifications
You must be signed in to change notification settings - Fork 23
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
Transactional MlsProvider #327
Conversation
.into_iter() | ||
.map(|stored_group| MlsGroup::new(self, stored_group.id, stored_group.created_at_ns)) | ||
.collect()) | ||
Ok(EncryptedMessageStore::find_groups( |
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 perfectly fine with making all the store methods static, given how few of them ever need to call another store method.
There's probably a case to make that these should just be methods on the models Groups::find
instead of EncryptedMessageStore::find_groups
. Probably more a matter of taste. Having them all be methods on one big store does limit the number of imports needed, which is simpler.
Good to leave as is setup in this PR if everyone else feels the same way.
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 don't have a problem reverting back if there are strong feelings around it, but it does clean up some of the fields in structs as well
I'm not sure how much more the store will grow, but considering we already have most of the methods within their model files, it shouldn't be difficult to refactor if we decide that pulling in all the methods is too much down the line
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.
Good to leave it for now, and we can re-open this discussion if the EncryptedMessageStore
keeps growing out of control and becomes unwieldy.
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 was actually wondering if these made sense as non-static methods on the connection. Every DB operation requires a connection anyway. But happy with this too
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.
That's what Wire does and the ergonomics actually seem pretty nice to me
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.
Essentially we would then just be wrapping diesel in a NewType and abstracting it away which I would generally be in favor of. I wasn't trying to re-do the design too much here, just figured making these methods static makes it easier to think of the store as just something that provides connections and makes db operations, without having to worry about where to access it
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 can exp with this tonight
/// provider.conn().borrow_mut() | ||
/// }) | ||
/// ``` | ||
pub fn transaction<T, F, E>(connection: &mut DbConnection, fun: F) -> Result<T, E> |
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 could see this being very useful. Smart addition.
This sounds fine. My feeling is that we are going to want anything that touches the |
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! I'm curious about what the tradeoff is of doing it this way with dispensable keystores versus having a long-lived keystore that always uses the same connection, but this works
|
I think either way, we'd have to have some function that starts a transaction with a mutable connection in a key store in order to remain scoped to one conn/provider. Quickly thinking through it, but we could definitely add a layer that hands out transactional key stores, if we decide it would be helpful. If we're dealing with one &mut reference, the lifetimes and borrow errors should be easier to deal with if we don't require things to live for too long. It's still possible to do, the tricky part is just satisfying diesels transaction reqs. |
Not necessarily one db connection, but just one connection in one transaction. Currently, it's easy to use the connection on provider rather than requesting a new connection from the pool if there's any provider type around. If there is lots of nested code using this conn, then it's easy to run into BorrowMut Errors at runtime b/c of the RefCell. In order to enforce only using the connection from provider when in the transaction, I was testing out adding a new struct, I ran into some issues in
I do think it would be worth looking into this, it might make things much cleaner and easier to handle w.r.t to the lifetime of the DbConnection. Might have to be careful of pulling too many DbConnections at once. Although i'm not sure what you mean by returning the struct from XmtpOpenMlsProvider. Would this create an interface encapsulating each database item? i.e
Or something else? |
Do you know if the tests were using an ephemeral or persistent DB store? For ephemeral store, there is a pool size of 1, because any additional connections seem to make a new in-memory store rather than writing to the same one.
Something like this - it might be a struct called something like |
There are a few key changes in this PR. There are also some things that definitely could be better, but overall I think it's good. I'm happy to accept feedback if there are things that should be better
&mut DbConnection
. We need this to be mutable b/c of diesels transaction design, which strictly requires a mutable reference. Transactions are started withXmtpOpenMlsProvider
, which provides aprovider
that is able to access the underlying connection withprovider.conn().borrow_mut()
RefCell
. This is mostly for performance/remain close to the DbConnection !Sync bound, since we're doing mostly single-threaded work. I thought it was OK to make it a RefCell at first anyway, since making it a Mutex isn't so different.Connection
trait. We can control where/how the database locks to some extent, however, through more specific Sqlite Transaction methods: methodsIn a follow up PR, it might be good to:
conn()
method to only be available within a transaction via a XmtpOpenMlsProvider wrapper type. This makes it much more difficult to run into borrow errors resulting from the underlying RefCell type. And, if using a mutex, run into deadlocks. For now we should be extra careful about borrowing the underlying connection.