mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: fix concurrent CommitQuota transactions for unrelated users/orgs (#15261)
The failure condition being fixed is `w1` and `w2` could belong to different users, organizations, and templates and still cause a serializable failure if run concurrently. This is because the old query did a `seq scan` on the `workspace_builds` table. Since that is the table being updated, we really want to prevent that. So before this would fail for any 2 workspaces. Now it only fails if `w1` and `w2` are owned by the same user and organization.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/util/slice"
|
||||
)
|
||||
|
||||
// PGLock docs see: https://www.postgresql.org/docs/current/view-pg-locks.html#VIEW-PG-LOCKS
|
||||
type PGLock struct {
|
||||
// LockType see: https://www.postgresql.org/docs/current/monitoring-stats.html#WAIT-EVENT-LOCK-TABLE
|
||||
LockType *string `db:"locktype"`
|
||||
Database *string `db:"database"` // oid
|
||||
Relation *string `db:"relation"` // oid
|
||||
RelationName *string `db:"relation_name"`
|
||||
Page *int `db:"page"`
|
||||
Tuple *int `db:"tuple"`
|
||||
VirtualXID *string `db:"virtualxid"`
|
||||
TransactionID *string `db:"transactionid"` // xid
|
||||
ClassID *string `db:"classid"` // oid
|
||||
ObjID *string `db:"objid"` // oid
|
||||
ObjSubID *int `db:"objsubid"`
|
||||
VirtualTransaction *string `db:"virtualtransaction"`
|
||||
PID int `db:"pid"`
|
||||
Mode *string `db:"mode"`
|
||||
Granted bool `db:"granted"`
|
||||
FastPath *bool `db:"fastpath"`
|
||||
WaitStart *time.Time `db:"waitstart"`
|
||||
}
|
||||
|
||||
func (l PGLock) Equal(b PGLock) bool {
|
||||
// Lazy, but hope this works
|
||||
return reflect.DeepEqual(l, b)
|
||||
}
|
||||
|
||||
func (l PGLock) String() string {
|
||||
granted := "granted"
|
||||
if !l.Granted {
|
||||
granted = "waiting"
|
||||
}
|
||||
var details string
|
||||
switch safeString(l.LockType) {
|
||||
case "relation":
|
||||
details = ""
|
||||
case "page":
|
||||
details = fmt.Sprintf("page=%d", *l.Page)
|
||||
case "tuple":
|
||||
details = fmt.Sprintf("page=%d tuple=%d", *l.Page, *l.Tuple)
|
||||
case "virtualxid":
|
||||
details = "waiting to acquire virtual tx id lock"
|
||||
default:
|
||||
details = "???"
|
||||
}
|
||||
return fmt.Sprintf("%d-%5s [%s] %s/%s/%s: %s",
|
||||
l.PID,
|
||||
safeString(l.TransactionID),
|
||||
granted,
|
||||
safeString(l.RelationName),
|
||||
safeString(l.LockType),
|
||||
safeString(l.Mode),
|
||||
details,
|
||||
)
|
||||
}
|
||||
|
||||
// PGLocks returns a list of all locks in the database currently in use.
|
||||
func (q *sqlQuerier) PGLocks(ctx context.Context) (PGLocks, error) {
|
||||
rows, err := q.sdb.QueryContext(ctx, `
|
||||
SELECT
|
||||
relation::regclass AS relation_name,
|
||||
*
|
||||
FROM pg_locks;
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
var locks []PGLock
|
||||
err = sqlx.StructScan(rows, &locks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return locks, err
|
||||
}
|
||||
|
||||
type PGLocks []PGLock
|
||||
|
||||
func (l PGLocks) String() string {
|
||||
// Try to group things together by relation name.
|
||||
sort.Slice(l, func(i, j int) bool {
|
||||
return safeString(l[i].RelationName) < safeString(l[j].RelationName)
|
||||
})
|
||||
|
||||
var out strings.Builder
|
||||
for i, lock := range l {
|
||||
if i != 0 {
|
||||
_, _ = out.WriteString("\n")
|
||||
}
|
||||
_, _ = out.WriteString(lock.String())
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// Difference returns the difference between two sets of locks.
|
||||
// This is helpful to determine what changed between the two sets.
|
||||
func (l PGLocks) Difference(to PGLocks) (new PGLocks, removed PGLocks) {
|
||||
return slice.SymmetricDifferenceFunc(l, to, func(a, b PGLock) bool {
|
||||
return a.Equal(b)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user