Always reuse transaction (#22362)
This commit is contained in:
parent
d42b52fcfa
commit
6135359a04
11 changed files with 104 additions and 42 deletions
|
@ -141,7 +141,7 @@ func CountNotifications(ctx context.Context, opts *FindNotificationOptions) (int
|
|||
|
||||
// CreateRepoTransferNotification creates notification for the user a repository was transferred to
|
||||
func CreateRepoTransferNotification(ctx context.Context, doer, newOwner *user_model.User, repo *repo_model.Repository) error {
|
||||
return db.AutoTx(ctx, func(ctx context.Context) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
var notify []*Notification
|
||||
|
||||
if newOwner.IsOrganization() {
|
||||
|
|
|
@ -71,6 +71,14 @@ type Engined interface {
|
|||
|
||||
// GetEngine will get a db Engine from this context or return an Engine restricted to this context
|
||||
func GetEngine(ctx context.Context) Engine {
|
||||
if e := getEngine(ctx); e != nil {
|
||||
return e
|
||||
}
|
||||
return x.Context(ctx)
|
||||
}
|
||||
|
||||
// getEngine will get a db Engine from this context or return nil
|
||||
func getEngine(ctx context.Context) Engine {
|
||||
if engined, ok := ctx.(Engined); ok {
|
||||
return engined.Engine()
|
||||
}
|
||||
|
@ -78,7 +86,7 @@ func GetEngine(ctx context.Context) Engine {
|
|||
if enginedInterface != nil {
|
||||
return enginedInterface.(Engined).Engine()
|
||||
}
|
||||
return x.Context(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Committer represents an interface to Commit or Close the Context
|
||||
|
@ -87,10 +95,22 @@ type Committer interface {
|
|||
Close() error
|
||||
}
|
||||
|
||||
// TxContext represents a transaction Context
|
||||
// halfCommitter is a wrapper of Committer.
|
||||
// It can be closed early, but can't be committed early, it is useful for reusing a transaction.
|
||||
type halfCommitter struct {
|
||||
Committer
|
||||
}
|
||||
|
||||
func (*halfCommitter) Commit() error {
|
||||
// do nothing
|
||||
return nil
|
||||
}
|
||||
|
||||
// TxContext represents a transaction Context,
|
||||
// it will reuse the existing transaction in the parent context or create a new one.
|
||||
func TxContext(parentCtx context.Context) (*Context, Committer, error) {
|
||||
if InTransaction(parentCtx) {
|
||||
return nil, nil, ErrAlreadyInTransaction
|
||||
if sess, ok := inTransaction(parentCtx); ok {
|
||||
return newContext(parentCtx, sess, true), &halfCommitter{Committer: sess}, nil
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
|
@ -102,20 +122,11 @@ func TxContext(parentCtx context.Context) (*Context, Committer, error) {
|
|||
return newContext(DefaultContext, sess, true), sess, nil
|
||||
}
|
||||
|
||||
// WithTx represents executing database operations on a transaction
|
||||
// This function will always open a new transaction, if a transaction exist in parentCtx return an error.
|
||||
func WithTx(parentCtx context.Context, f func(ctx context.Context) error) error {
|
||||
if InTransaction(parentCtx) {
|
||||
return ErrAlreadyInTransaction
|
||||
}
|
||||
return txWithNoCheck(parentCtx, f)
|
||||
}
|
||||
|
||||
// AutoTx represents executing database operations on a transaction, if the transaction exist,
|
||||
// WithTx represents executing database operations on a transaction, if the transaction exist,
|
||||
// this function will reuse it otherwise will create a new one and close it when finished.
|
||||
func AutoTx(parentCtx context.Context, f func(ctx context.Context) error) error {
|
||||
if InTransaction(parentCtx) {
|
||||
return f(newContext(parentCtx, GetEngine(parentCtx), true))
|
||||
func WithTx(parentCtx context.Context, f func(ctx context.Context) error) error {
|
||||
if sess, ok := inTransaction(parentCtx); ok {
|
||||
return f(newContext(parentCtx, sess, true))
|
||||
}
|
||||
return txWithNoCheck(parentCtx, f)
|
||||
}
|
||||
|
@ -202,25 +213,25 @@ func EstimateCount(ctx context.Context, bean interface{}) (int64, error) {
|
|||
|
||||
// InTransaction returns true if the engine is in a transaction otherwise return false
|
||||
func InTransaction(ctx context.Context) bool {
|
||||
var e Engine
|
||||
if engined, ok := ctx.(Engined); ok {
|
||||
e = engined.Engine()
|
||||
} else {
|
||||
enginedInterface := ctx.Value(enginedContextKey)
|
||||
if enginedInterface != nil {
|
||||
e = enginedInterface.(Engined).Engine()
|
||||
}
|
||||
}
|
||||
_, ok := inTransaction(ctx)
|
||||
return ok
|
||||
}
|
||||
|
||||
func inTransaction(ctx context.Context) (*xorm.Session, bool) {
|
||||
e := getEngine(ctx)
|
||||
if e == nil {
|
||||
return false
|
||||
return nil, false
|
||||
}
|
||||
|
||||
switch t := e.(type) {
|
||||
case *xorm.Engine:
|
||||
return false
|
||||
return nil, false
|
||||
case *xorm.Session:
|
||||
return t.IsInTx()
|
||||
if t.IsInTx() {
|
||||
return t, true
|
||||
}
|
||||
return nil, false
|
||||
default:
|
||||
return false
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,8 +25,62 @@ func TestInTransaction(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
defer committer.Close()
|
||||
assert.True(t, db.InTransaction(ctx))
|
||||
assert.Error(t, db.WithTx(ctx, func(ctx context.Context) error {
|
||||
assert.NoError(t, db.WithTx(ctx, func(ctx context.Context) error {
|
||||
assert.True(t, db.InTransaction(ctx))
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
||||
func TestTxContext(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
{ // create new transaction
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, db.InTransaction(ctx))
|
||||
assert.NoError(t, committer.Commit())
|
||||
}
|
||||
|
||||
{ // reuse the transaction created by TxContext and commit it
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
engine := db.GetEngine(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, db.InTransaction(ctx))
|
||||
{
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, db.InTransaction(ctx))
|
||||
assert.Equal(t, engine, db.GetEngine(ctx))
|
||||
assert.NoError(t, committer.Commit())
|
||||
}
|
||||
assert.NoError(t, committer.Commit())
|
||||
}
|
||||
|
||||
{ // reuse the transaction created by TxContext and close it
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
engine := db.GetEngine(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, db.InTransaction(ctx))
|
||||
{
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, db.InTransaction(ctx))
|
||||
assert.Equal(t, engine, db.GetEngine(ctx))
|
||||
assert.NoError(t, committer.Close())
|
||||
}
|
||||
assert.NoError(t, committer.Close())
|
||||
}
|
||||
|
||||
{ // reuse the transaction created by WithTx
|
||||
assert.NoError(t, db.WithTx(db.DefaultContext, func(ctx context.Context) error {
|
||||
assert.True(t, db.InTransaction(ctx))
|
||||
{
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, db.InTransaction(ctx))
|
||||
assert.NoError(t, committer.Commit())
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,14 +4,11 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
var ErrAlreadyInTransaction = errors.New("database connection has already been in a transaction")
|
||||
|
||||
// ErrCancelled represents an error due to context cancellation
|
||||
type ErrCancelled struct {
|
||||
Message string
|
||||
|
|
|
@ -2365,7 +2365,7 @@ func CountOrphanedIssues(ctx context.Context) (int64, error) {
|
|||
// DeleteOrphanedIssues delete issues without a repo
|
||||
func DeleteOrphanedIssues(ctx context.Context) error {
|
||||
var attachmentPaths []string
|
||||
err := db.AutoTx(ctx, func(ctx context.Context) error {
|
||||
err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
var ids []int64
|
||||
|
||||
if err := db.GetEngine(ctx).Table("issue").Distinct("issue.repo_id").
|
||||
|
|
|
@ -300,7 +300,7 @@ func changeProjectStatus(ctx context.Context, p *Project, isClosed bool) error {
|
|||
// DeleteProjectByID deletes a project from a repository. if it's not in a database
|
||||
// transaction, it will start a new database transaction
|
||||
func DeleteProjectByID(ctx context.Context, id int64) error {
|
||||
return db.AutoTx(ctx, func(ctx context.Context) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
p, err := GetProjectByID(ctx, id)
|
||||
if err != nil {
|
||||
if IsErrProjectNotExist(err) {
|
||||
|
|
|
@ -105,7 +105,7 @@ func ChangeCollaborationAccessMode(ctx context.Context, repo *Repository, uid in
|
|||
return nil
|
||||
}
|
||||
|
||||
return db.AutoTx(ctx, func(ctx context.Context) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
collaboration := &Collaboration{
|
||||
|
|
|
@ -155,7 +155,7 @@ func TestRepositoryReadyForTransfer(status repo_model.RepositoryStatus) error {
|
|||
// CreatePendingRepositoryTransfer transfer a repo from one owner to a new one.
|
||||
// it marks the repository transfer as "pending"
|
||||
func CreatePendingRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.User, repoID int64, teams []*organization.Team) error {
|
||||
return db.AutoTx(ctx, func(ctx context.Context) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
repo, err := repo_model.GetRepositoryByID(ctx, repoID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -243,7 +243,7 @@ func (ns *notificationService) NotifyPullReviewRequest(ctx context.Context, doer
|
|||
}
|
||||
|
||||
func (ns *notificationService) NotifyRepoPendingTransfer(ctx context.Context, doer, newOwner *user_model.User, repo *repo_model.Repository) {
|
||||
err := db.AutoTx(ctx, func(ctx context.Context) error {
|
||||
err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
return activities_model.CreateRepoTransferNotification(ctx, doer, newOwner, repo)
|
||||
})
|
||||
if err != nil {
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
)
|
||||
|
||||
func AddCollaborator(ctx context.Context, repo *repo_model.Repository, u *user_model.User) error {
|
||||
return db.AutoTx(ctx, func(ctx context.Context) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
collaboration := &repo_model.Collaboration{
|
||||
RepoID: repo.ID,
|
||||
UserID: u.ID,
|
||||
|
|
|
@ -123,7 +123,7 @@ func UpdateComment(ctx context.Context, c *issues_model.Comment, doer *user_mode
|
|||
|
||||
// DeleteComment deletes the comment
|
||||
func DeleteComment(ctx context.Context, doer *user_model.User, comment *issues_model.Comment) error {
|
||||
err := db.AutoTx(ctx, func(ctx context.Context) error {
|
||||
err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
return issues_model.DeleteComment(ctx, comment)
|
||||
})
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in a new issue