Skip to content

chore: increase parallelism of TestWorkspaceQuota #6710

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

Merged
merged 5 commits into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Seralizable transactions should retry automatically
  • Loading branch information
Emyrk committed Mar 21, 2023
commit a8e256ac499ffdab30f92697dbf6eef0a43b2840
36 changes: 35 additions & 1 deletion coderd/database/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,42 @@ func (q *sqlQuerier) Ping(ctx context.Context) (time.Duration, error) {
return time.Since(start), err
}

// InTx performs database operations inside a transaction.
func (q *sqlQuerier) InTx(function func(Store) error, txOpts *sql.TxOptions) error {
_, inTx := q.db.(*sqlx.Tx)
isolation := sql.LevelDefault
if txOpts != nil {
isolation = txOpts.Isolation
}

// If we are not already in a transaction, and we are running in serializable
// mode, we need to run the transaction in a retry loop. The caller should be
// prepared to allow retries if using serializable mode.
// If we are in a transaction already, the parent InTx call will handle the retry.
// We do not want to duplicate those retries.
if !inTx && isolation == sql.LevelSerializable {
// This is an arbitrarily chosen number.
const retryAmount = 3
var err error
attempts := 0
for attempts = 0; attempts < retryAmount; attempts++ {
err = q.runTx(function, txOpts)
if err == nil {
// Transaction succeeded.
return nil
}
if err != nil && !IsSerializedError(err) {
// We should only retry if the error is a serialization error.
return err
}
}
// Transaction kept failing in serializable mode.
return xerrors.Errorf("transaction failed after %d attempts: %w", attempts, err)
}
return q.runTx(function, txOpts)
}

// InTx performs database operations inside a transaction.
func (q *sqlQuerier) runTx(function func(Store) error, txOpts *sql.TxOptions) error {
if _, ok := q.db.(*sqlx.Tx); ok {
// If the current inner "db" is already a transaction, we just reuse it.
// We do not need to handle commit/rollback as the outer tx will handle
Expand Down
31 changes: 31 additions & 0 deletions coderd/database/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,44 @@ import (
"testing"

"github.com/google/uuid"
"github.com/lib/pq"
"github.com/stretchr/testify/require"

"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/database/migrations"
"github.com/coder/coder/coderd/database/postgres"
)

func TestSerializedRetry(t *testing.T) {
t.Parallel()
if testing.Short() {
t.SkipNow()
}

sqlDB := testSQLDB(t)
db := database.New(sqlDB)

called := 0
txOpts := &sql.TxOptions{Isolation: sql.LevelSerializable}
err := db.InTx(func(tx database.Store) error {
// Test nested error
return tx.InTx(func(tx database.Store) error {
// The easiest way to mock a serialization failure is to
// return a serialization failure error.
called++
return &pq.Error{
Code: "40001",
Message: "serialization_failure",
}
}, txOpts)
}, txOpts)
require.Error(t, err, "should fail")
// The double "execute transaction: execute transaction" is from the nested transactions.
// Just want to make sure we don't try 9 times.
require.Equal(t, err.Error(), "transaction failed after 3 attempts: execute transaction: execute transaction: pq: serialization_failure", "error message")
require.Equal(t, called, 3, "should retry 3 times")
}

func TestNestedInTx(t *testing.T) {
t.Parallel()
if testing.Short() {
Expand Down
8 changes: 8 additions & 0 deletions coderd/database/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ import (
"github.com/lib/pq"
)

func IsSerializedError(err error) bool {
var pqErr *pq.Error
if errors.As(err, &pqErr) {
return pqErr.Code.Name() == "serialization_failure"
}
return false
}

// IsUniqueViolation checks if the error is due to a unique violation.
// If one or more specific unique constraints are given as arguments,
// the error must be caused by one of them. If no constraints are given,
Expand Down