Add Test:
1) TestPreparedStmtConcurrentReset 2) TestPreparedStmtConcurrentClose
This commit is contained in:
		
							parent
							
								
									9229d83a88
								
							
						
					
					
						commit
						39a938766a
					
				| @ -4,6 +4,7 @@ import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| 
 | ||||
| @ -167,3 +168,141 @@ func TestPreparedStmtReset(t *testing.T) { | ||||
| 		t.Fatalf("prepared stmt should be empty") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func isUsingClosedConnError(err error) bool { | ||||
| 	// https://github.com/golang/go/blob/e705a2d16e4ece77e08e80c168382cdb02890f5b/src/database/sql/sql.go#L2717
 | ||||
| 	return err.Error() == "sql: statement is closed" | ||||
| } | ||||
| 
 | ||||
| // TestPreparedStmtConcurrentReset test calling reset and executing SQL concurrently
 | ||||
| // this test making sure that the gorm would not get a Segmentation Fault, and the only error cause by this is using a closed Stmt
 | ||||
| func TestPreparedStmtConcurrentReset(t *testing.T) { | ||||
| 
 | ||||
| 	tx := DB.Session(&gorm.Session{PrepareStmt: true}) | ||||
| 	pdb, ok := tx.ConnPool.(*gorm.PreparedStmtDB) | ||||
| 	if !ok { | ||||
| 		t.Fatalf("should assign PreparedStatement Manager back to database when using PrepareStmt mode") | ||||
| 	} | ||||
| 
 | ||||
| 	loopCount := 100 | ||||
| 	var wg sync.WaitGroup | ||||
| 	var unexpectedError bool | ||||
| 	writerFinish := make(chan struct{}) | ||||
| 
 | ||||
| 	name := "prepared_stmt_concurrent_reset" | ||||
| 	user := *GetUser(name, Config{}) | ||||
| 	tx = tx.Create(&user) | ||||
| 	if tx.Error != nil { | ||||
| 		t.Fatalf("failed to prepare record due to %s, test cannot be continue", tx.Error) | ||||
| 	} | ||||
| 	wg.Add(1) | ||||
| 	go func(id uint) { | ||||
| 		defer wg.Done() | ||||
| 		defer close(writerFinish) | ||||
| 
 | ||||
| 		for j := 0; j < loopCount; j++ { | ||||
| 			var tmp User | ||||
| 			err := tx.Session(&gorm.Session{}).First(&tmp, id).Error | ||||
| 			if err == nil || isUsingClosedConnError(err) { | ||||
| 				continue | ||||
| 			} | ||||
| 			t.Errorf("failed to read user of id %d due to %s, there should not be error", id, err) | ||||
| 			unexpectedError = true | ||||
| 			break | ||||
| 		} | ||||
| 	}(user.ID) | ||||
| 
 | ||||
| 	wg.Add(1) | ||||
| 	go func() { | ||||
| 		defer wg.Done() | ||||
| 		<-writerFinish | ||||
| 		pdb.Reset() | ||||
| 	}() | ||||
| 
 | ||||
| 	wg.Wait() | ||||
| 
 | ||||
| 	if unexpectedError { | ||||
| 		t.Fatalf("should is a unexpected error") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // TestPreparedStmtConcurrentClose test calling close and executing SQL concurrently
 | ||||
| // for example: one goroutine found error and just close the database, and others are executing SQL
 | ||||
| // this test making sure that the gorm would not get a Segmentation Fault,
 | ||||
| // and the only error cause by this is using a closed Stmt or gorm.ErrInvalidDB
 | ||||
| // and all of the goroutine must got gorm.ErrInvalidDB after database close
 | ||||
| func TestPreparedStmtConcurrentClose(t *testing.T) { | ||||
| 
 | ||||
| 	tx := DB.Session(&gorm.Session{PrepareStmt: true}) | ||||
| 	pdb, ok := tx.ConnPool.(*gorm.PreparedStmtDB) | ||||
| 	if !ok { | ||||
| 		t.Fatalf("should assign PreparedStatement Manager back to database when using PrepareStmt mode") | ||||
| 	} | ||||
| 
 | ||||
| 	loopCount := 100 | ||||
| 	var wg sync.WaitGroup | ||||
| 	var lastErr error | ||||
| 	closeValid := make(chan struct{}, loopCount) | ||||
| 	closeStartIdx := loopCount / 2 // close the database at the middle of the execution
 | ||||
| 	var lastRunIndex int | ||||
| 	var closeFinishedAt int64 | ||||
| 
 | ||||
| 	name := "prepared_stmt_concurrent_close" | ||||
| 	user := *GetUser(name, Config{}) | ||||
| 	tx = tx.Create(&user) | ||||
| 	if tx.Error != nil { | ||||
| 		t.Fatalf("failed to prepare record due to %s, test cannot be continue", tx.Error) | ||||
| 	} | ||||
| 	wg.Add(1) | ||||
| 	go func(id uint) { | ||||
| 		defer wg.Done() | ||||
| 		defer close(closeValid) | ||||
| 		for lastRunIndex = 1; lastRunIndex <= loopCount; lastRunIndex++ { | ||||
| 			if lastRunIndex == closeStartIdx { | ||||
| 				closeValid <- struct{}{} | ||||
| 			} | ||||
| 			var tmp User | ||||
| 			now := time.Now().UnixNano() | ||||
| 			err := tx.Session(&gorm.Session{}).First(&tmp, id).Error | ||||
| 			if err == nil { | ||||
| 				closeFinishedAt := atomic.LoadInt64(&closeFinishedAt) | ||||
| 				if (closeFinishedAt != 0) && (now > closeFinishedAt) { | ||||
| 					lastErr = errors.New("must got error after database closed") | ||||
| 					break | ||||
| 				} | ||||
| 				continue | ||||
| 			} | ||||
| 			lastErr = err | ||||
| 			break | ||||
| 		} | ||||
| 	}(user.ID) | ||||
| 
 | ||||
| 	wg.Add(1) | ||||
| 	go func() { | ||||
| 		defer wg.Done() | ||||
| 		for range closeValid { | ||||
| 			for i := 0; i < loopCount; i++ { | ||||
| 				pdb.Close() // the Close method must can be call multiple times
 | ||||
| 				atomic.CompareAndSwapInt64(&closeFinishedAt, 0, time.Now().UnixNano()) | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	wg.Wait() | ||||
| 	var tmp User | ||||
| 	err := tx.Session(&gorm.Session{}).First(&tmp, user.ID).Error | ||||
| 	if err != gorm.ErrInvalidDB { | ||||
| 		t.Fatalf("must got a gorm.ErrInvalidDB while execution after db close, got %+v instead", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// must be error
 | ||||
| 	if lastErr != gorm.ErrInvalidDB && !isUsingClosedConnError(lastErr) { | ||||
| 		t.Fatalf("exp error gorm.ErrInvalidDB, got %+v instead", lastErr) | ||||
| 	} | ||||
| 	if lastRunIndex >= loopCount || lastRunIndex < closeStartIdx { | ||||
| 		t.Fatalf("exp loop times between (closeStartIdx %d <=) and (< loopCount %d), got %d instead", closeStartIdx, loopCount, lastRunIndex) | ||||
| 	} | ||||
| 	if pdb.Stmts != nil { | ||||
| 		t.Fatalf("stmts must be nil") | ||||
| 	} | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Zehui Chen
						Zehui Chen