fix(11-08): refuse unmanaged after-commit work

This commit is contained in:
Jakub Zych
2026-09-30 20:14:42 +02:00
parent f7b6b0c313
commit a33b1ada80
4 changed files with 66 additions and 23 deletions

View File

@@ -52,6 +52,9 @@ func Transaction(ctx context.Context, gdb *gorm.DB, fn func(ctx context.Context,
ctx = context.Background()
}
if parent, ok := ctx.Value(afterCommitKey{}).(*afterCommitBuffer); ok && parent != nil {
if !transactionalHandle(gdb) {
return fmt.Errorf("lagoon: nested transaction requires the parent transaction handle")
}
child := &afterCommitBuffer{}
childCtx := context.WithValue(ctx, afterCommitKey{}, child)
err := gdb.WithContext(childCtx).Transaction(func(tx *gorm.DB) error {
@@ -79,8 +82,9 @@ func Transaction(ctx context.Context, gdb *gorm.DB, fn func(ctx context.Context,
// single-statement write for which GORM opened its own transaction (for
// example from a GORM create callback) it runs after that commit through the
// AfterCommitCallback callback, and not at all when the write fails.
// Anywhere else, including inside a plain gorm Transaction, fn runs
// immediately on db's connection.
// Inside a foreign plain GORM transaction it logs a warning and refuses to
// run, because Lagoon cannot know whether that transaction will commit.
// Anywhere else, fn runs immediately on db's connection.
//
// The handle fn receives always has an empty statement on the connection
// the work belongs to (the pool after a commit, the open transaction
@@ -113,10 +117,22 @@ func AfterCommit(ctx context.Context, db *gorm.DB, fn func(ctx context.Context,
db.InstanceSet(statementBufferKey, buf)
return
}
if transactionalHandle(db) {
slog.Default().Warn("lagoon: after-commit callback skipped inside unmanaged transaction")
return
}
}
runAfterCommit(ctx, cleanHandle(db, ctx), []func(context.Context, *gorm.DB){fn})
}
func transactionalHandle(db *gorm.DB) bool {
if db == nil || db.Statement == nil || db.Statement.ConnPool == nil {
return false
}
_, ok := db.Statement.ConnPool.(gorm.TxCommitter)
return ok
}
func bufferFrom(ctx context.Context, db *gorm.DB) *afterCommitBuffer {
if buf, ok := ctx.Value(afterCommitKey{}).(*afterCommitBuffer); ok && buf != nil {
return buf