Files
upstream-mirror 7e385fc126
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
2026-09-18 01:22:23 +05:00

101 lines
2.8 KiB
Go

package rdbms
import (
"context"
"database/sql"
"fmt"
"time"
"github.com/cortezaproject/corteza/server/pkg/errors"
"github.com/cortezaproject/corteza/server/store"
"github.com/jmoiron/sqlx"
)
type (
dbTransactionMaker interface {
BeginTxx(ctx context.Context, opts *sql.TxOptions) (*sqlx.Tx, error)
}
)
func (s *Store) Tx(ctx context.Context, fn func(context.Context, store.Storer) error) error {
if s.TxRetryLimit < 0 {
return fn(ctx, s)
}
return txHandler(ctx, s.DB, s.TxRetryLimit, s.TxRetryErrHandler, func(ctx context.Context, tx sqlx.ExtContext) error {
return fn(ctx, s.withTx(tx))
})
}
// tx begins a new db transaction and handles retries when possible
//
// It utilizes configured transaction error handlers and max-retry limits
// to determine if and how many times transaction should be retried
//
func txHandler(ctx context.Context, dbc interface{}, max int, reh txRetryOnErrHandler, task func(context.Context, sqlx.ExtContext) error) error {
var (
lastTaskErr error
err error
db *sqlx.DB
tx *sqlx.Tx
try = 1
)
switch dbc.(type) {
case dbTransactionMaker:
// we can make a transaction!
db = dbc.(*sqlx.DB)
case sqlx.ExtContext:
// Already in a transaction, run the given task and finish
return task(ctx, dbc.(sqlx.ExtContext))
default:
return fmt.Errorf("could not use the db connection for transaction")
}
for {
try++
// Start transaction
tx, err = db.BeginTxx(ctx, nil)
if err != nil {
return err
}
if lastTaskErr = task(ctx, tx); lastTaskErr == nil {
// Task completed successfully
return tx.Commit()
}
// No matter the cause of the error, rollback the transaction
if rollbackErr := tx.Rollback(); rollbackErr != nil {
return fmt.Errorf("failed to rollback transaction (tries: %d) on error %v: %w", try, lastTaskErr, rollbackErr)
}
if errors.IsAny(lastTaskErr) {
// not a store-internal/transaction error and can be returned right away,
// no need to re-run the transaction
return lastTaskErr
}
// Call the configured transaction retry error handler
// if this particular error should be retried or not
//
// We cannot generalize here because different store implementation have
// different errors; we need to act accordingly
if reh != nil && !reh(try, lastTaskErr) {
return fmt.Errorf("failed to complete transaction: %w", lastTaskErr)
}
// tx error handlers can take current number of tries into account and
// break the retry-loop earlier, but that might not be always the case
//
// We'll check the configured and hard-limit maximums
if try >= max || try >= TxRetryHardLimit {
return fmt.Errorf("failed to perform transaction (tries: %d), last error: %w", try, lastTaskErr)
}
// Sleep (with a bit of kickback) before doing next retry
time.Sleep(50 * time.Duration(try*50))
}
}