// createTestTable tries to create a new table named based on the passed in id. // It is designed to be synced with a number of concurrent calls to this // function. Before starting, it first signals a done on the start waitgroup // and then will block until the signal channel is closed. Once closed, it will // proceed to try to create the table. Once the table creation is finished (be // it successful or not) it signals a done on the end waitgroup. func createTestTable( t *testing.T, tc *testcluster.TestCluster, id int, db *gosql.DB, wgStart *sync.WaitGroup, wgEnd *sync.WaitGroup, signal chan struct{}, completed chan int, ) { defer wgEnd.Done() wgStart.Done() <-signal tableSQL := fmt.Sprintf(` CREATE TABLE IF NOT EXISTS "test"."table_%d" ( id INT PRIMARY KEY, val INT )`, id) for { if _, err := db.Exec(tableSQL); err != nil { if testutils.IsSQLRetryableError(err) { continue } t.Errorf("table %d: could not be created: %v", id, err) return } completed <- id break } }
// queryRowScan performs first a QueryRow and follows that up with a Scan using // a preexisting or new connection. func (dc *dynamicClient) queryRowScan(query string, queryArgs, destArgs []interface{}) error { for dc.isRunning() { client, err := dc.getClient() if err != nil { return err } if err := client.QueryRow( query, queryArgs..., ).Scan(destArgs...); err == nil || !testutils.IsSQLRetryableError(err) { return err } } return errTestFinished }
// exec calls exec on a client using a preexisting or new connection. func (dc *dynamicClient) exec(query string, args ...interface{}) (gosql.Result, error) { for dc.isRunning() { client, err := dc.getClient() if err != nil { return nil, err } if result, err := client.Exec( query, args..., ); err == nil || !testutils.IsSQLRetryableError(err) { return result, err } } return nil, errTestFinished }
// Verify accounts. func verifyAccounts(t *testing.T, client *testClient) { var sum int testutils.SucceedsSoon(t, func() error { // Hold the read lock on the client to prevent it being restarted by // chaos monkey. client.RLock() defer client.RUnlock() err := client.db.QueryRow("SELECT SUM(balance) FROM bank.accounts").Scan(&sum) if err != nil && !testutils.IsSQLRetryableError(err) { t.Fatal(err) } return err }) if sum != 0 { t.Fatalf("The bank is not in good order. Total value: %d", sum) } }
// Continuously transfers money until done(). func transferMoneyLoop(idx int, state *testState, numAccounts, maxTransfer int) { client := &state.clients[idx] for !state.done() { if err := transferMoney(client, numAccounts, maxTransfer); err != nil { // Ignore some errors. if !testutils.IsSQLRetryableError(err) { // Report the err and terminate. state.errChan <- err break } } else { // Only advance the counts on a successful update. atomic.AddUint64(&client.count, 1) } } log.Infof(context.Background(), "client %d shutting down", idx) state.errChan <- nil }