I've built a small web service in Rust but there are still some rough edges. In particular, some endpoints can fail (not a
panic!, just a normal failure from talking to other services which is signaled in my Rocket app using a custom
Responder). When this happens, the database is left in an inconsistent state. I'd like to roll back transactions in all outstanding database connections. I'm stuck on how to make that ergonomic. I can't have the connections do cleanup on
Drop because I need to know what the response is before choosing to commit or roll back. I also don't like the Responder type having explicit access to all connections; that seems like it requires the programmer to be careful to keep track of their connections themselves.
The idea that I know which connections are outstanding suggests I have a "registry" of outstanding connections. In Rocket, the most obvious way to keep that kind of registry is using
local_cache, which requires
Send + Sync on the cached value (i.e. the registry), which I guess suggests that the value is sent to another thread. I understand that there are obvious safety issues when sharing values between threads (implicit in the
Sync traits, but it seems to me that there's a clear "ownership" here -- the endpoint owns the connection until it drops it, and then ownership belongs to the "cleanup" thread. So I'm trying to put that rationale into practice. I have successfully defined an
OutstandingConnections (wrapper around
Vec) that is stored in the Request state using
local_cache, but I'm struggling to produce connections that my endpoint can use that are also accumulated in that
Things I've tried so far:
- Store each connection in an
Arcand, every time I'm about to hand out a database connection, clone the pointer and put it in the
OutstandingConnections. This doesn't work because the connections aren't
- Store each connection in a
Arc, and every time I'm about to hand out a database connection, clone the
Arcand put it in
OutstandingConnections. Unfortunately this means that the endpoint has to handle locking the
Mutexitself on every use, so I didn't really even try this.. Besides, it feels like I should be able to ensure that the mutex is available exclusively to the endpoint until it drops the handle.
- As above, but lock the Mutex and return the
MutexGuard. I ran into a bunch of lifetime issues with this. Naïvely I wanted to wrap the Mutex in an
Arc, add it to the
OutstandingConnections, lock it, and return the output from
lock(). I guess this didn't work because the compiler can't guarantee that
OutstandingConnectionswon't go out of scope in the meantime.
- As above but try to keep the Mutex, or an
Arcaround the mutex, alongside the MutexGuard. More lifetime troubles of a different kind. I eventually found posts like Keep ref to MutexGuard in struct, Integrate Mutex and MutexGuard into a struct which seem to imply that this is a dead-end. One post suggests that I could use
parking_lotbut that didn't seem ergonomic to me either and I was hesitant to introduce another dependency just yet.
- Some of these posts suggest that a better approach would be to accept a function that uses the locked value and does cleanup afterwards, but that isn't really feasible with Rocket's endpoint invocation machinery -- you have to return the thing you want the endpoint to have as an argument.
- Another idea I had was to try to have my wrapper around the connection have a custom Drop that, when it happened, transferred ownership to the
OutstandingConnections. But transferring ownership of a field from
dropis prevented by the borrow checker (there's still
&mut selfoutstanding), and I don't have a "dummy" connection I could swap in. I could wrap the connection with an
Option, but again, that means all uses have to handle the case where the connection isn't there.
- I found Moving out of a type implementing Drop, which suggests
ManuallyDrop, but that means
unsafeand I wanted to see if there were other ideas.
I'd also love to know whether this is an unusual pattern, or if there are any architectural lessons I should be taking away from this. I'm still shaky on ownership and lifetimes generally. Thanks!