feat: convenient wrapper for Postgres advisory locks #1641
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
While working on a Launchbadge project, I realized I needed a way to provide mutual exclusion synchronized with the database so that multiple instances of a server application don't try to perform the same side-effecting operation, but the locking scheme I wanted didn't fit neatly onto standard row- or table-level locking.
I excitedly remembered that Postgres has advisory locks with application-defined semantics, but quickly realized they were somewhat unwieldy to use for our purposes.
There's transaction-scoped advisory locks, which would work well except that because I was doing side-effecting operations, I wanted the updates I was making to the database to be published immediately, and it seemed unreasonable to check out two connections from the pool, with one purely existing to hold a transaction for automatically managed advisory locks.
There's also session-scoped advisory locks, which are held until manually released or until the connection is closed. This was better, but because I'm using
PgPool
I wanted to make sure the lock is released when the connection is returned to the pool. However, some ding-dong (i.e. me) decided that.after_release()
didn't need to support async functionality, because... I don't remember why.So I ended up writing a wrapper that spawns a task on drop to execute
pg_advisory_unlock()
on the connection before letting it return to the pool, so I could get the project working, but I immediately felt like this should be something that lives in SQLx, and could be more convenient to use and optimally implemented that way.Thus began a two-and-a-half hour nerd snipe that culminated in this.
TODO: