fix(11-07): roll back the savepoint when a swallowed read failed
- beachcomber and lighthouse released their savepoint whenever the inner function reported no error; a Gate that counts a failed read as off, or a channel function or delete snapshot that swallows one, left the caller's Postgres transaction aborted (25P02) and failed the write - a failed RELEASE now rolls back to the savepoint, as the READMEs promise - beachcomber gets its testcontainers harness and sync tests (TestSyncGates, TestSyncAfterCommit, TestSyncDeleteAndSoftDelete, TestSyncFailuresNonFatal, TestServiceSetup); lighthouse gets TestBroadcastSwallowedReadFailure
This commit is contained in:
@@ -379,6 +379,11 @@ func (s *Sprocket) BroadcastChannels(_ context.Context, tx *gorm.DB) ([]string,
|
||||
if s.Channels == "fail" {
|
||||
return nil, errors.New("channels failed")
|
||||
}
|
||||
if s.Channels == "swallow" {
|
||||
// A channel function that treats a failed read as "no channels".
|
||||
_ = tx.Exec(`SELECT * FROM acme_missing_table`).Error
|
||||
return nil, nil
|
||||
}
|
||||
if s.Channels == "abort-tx" {
|
||||
// A failed statement aborts a Postgres transaction unless it runs
|
||||
// inside a savepoint.
|
||||
@@ -750,6 +755,32 @@ func TestBroadcastEdges(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestBroadcastSwallowedReadFailure covers a channel function that
|
||||
// swallows a failed read: the savepoint must still be rolled back, or the
|
||||
// failed statement leaves the caller's transaction aborted.
|
||||
func TestBroadcastSwallowedReadFailure(t *testing.T) {
|
||||
env := newLHEnv(t, nil, nil)
|
||||
ctx := t.Context()
|
||||
err := lagoon.Transaction(ctx, env.gdb, func(ctx context.Context, tx *gorm.DB) error {
|
||||
if err := tx.WithContext(ctx).Create(&Sprocket{Name: "swallowed", Channels: "swallow"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.WithContext(ctx).Create(&Widget{Name: "after-swallow", OwnerID: 0}).Error
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("a swallowed read failure in the broadcast savepoint aborted the write: %v", err)
|
||||
}
|
||||
// A single-statement write runs its callbacks inside GORM's own
|
||||
// transaction; the commit must still succeed.
|
||||
if err := env.gdb.WithContext(ctx).Create(&Sprocket{Name: "swallowed-implicit", Channels: "swallow"}).Error; err != nil {
|
||||
t.Fatalf("single-statement write with a swallowed read failure: %v", err)
|
||||
}
|
||||
var n int64
|
||||
if err := env.gdb.Model(&Sprocket{}).Where("name LIKE ?", "swallowed%").Count(&n).Error; err != nil || n != 2 {
|
||||
t.Fatalf("committed sprockets = %d (err %v), want 2", n, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBroadcastPublishFailure covers D-09: a failed publish is logged at
|
||||
// Warn without the payload and the one-attempt job is not retried.
|
||||
func TestBroadcastPublishFailure(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user