github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/pingcap/go-themis/themis_txn.go (about)

     1  package themis
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/insionng/yougam/libraries/juju/errors"
     9  	"github.com/insionng/yougam/libraries/ngaut/log"
    10  	"github.com/insionng/yougam/libraries/pingcap/go-hbase"
    11  	"github.com/insionng/yougam/libraries/pingcap/go-themis/oracle"
    12  )
    13  
    14  type TxnConfig struct {
    15  	ConcurrentPrewriteAndCommit bool
    16  	WaitSecondaryCommit         bool
    17  	TTLInMs                     uint64
    18  	MaxRowsInOneTxn             int
    19  	// options below is for debugging and testing
    20  	brokenPrewriteSecondaryTest            bool
    21  	brokenPrewriteSecondaryAndRollbackTest bool
    22  	brokenCommitPrimaryTest                bool
    23  	brokenCommitSecondaryTest              bool
    24  }
    25  
    26  var defaultTxnConf = TxnConfig{
    27  	ConcurrentPrewriteAndCommit:            true,
    28  	WaitSecondaryCommit:                    false,
    29  	MaxRowsInOneTxn:                        50000,
    30  	TTLInMs:                                5 * 1000, // default txn TTL: 5s
    31  	brokenPrewriteSecondaryTest:            false,
    32  	brokenPrewriteSecondaryAndRollbackTest: false,
    33  	brokenCommitPrimaryTest:                false,
    34  	brokenCommitSecondaryTest:              false,
    35  }
    36  
    37  type themisTxn struct {
    38  	client             hbase.HBaseClient
    39  	rpc                *themisRPC
    40  	lockCleaner        LockManager
    41  	oracle             oracle.Oracle
    42  	mutationCache      *columnMutationCache
    43  	startTs            uint64
    44  	commitTs           uint64
    45  	primaryRow         *rowMutation
    46  	primary            *hbase.ColumnCoordinate
    47  	secondaryRows      []*rowMutation
    48  	secondary          []*hbase.ColumnCoordinate
    49  	primaryRowOffset   int
    50  	singleRowTxn       bool
    51  	secondaryLockBytes []byte
    52  	conf               TxnConfig
    53  	hooks              *txnHook
    54  }
    55  
    56  var _ Txn = (*themisTxn)(nil)
    57  
    58  var (
    59  	// ErrSimulated is used when maybe rollback occurs error too.
    60  	ErrSimulated           = errors.New("simulated error")
    61  	maxCleanLockRetryCount = 30
    62  	pauseTime              = 300 * time.Millisecond
    63  )
    64  
    65  func NewTxn(c hbase.HBaseClient, oracle oracle.Oracle) (Txn, error) {
    66  	return NewTxnWithConf(c, defaultTxnConf, oracle)
    67  }
    68  
    69  func NewTxnWithConf(c hbase.HBaseClient, conf TxnConfig, oracle oracle.Oracle) (Txn, error) {
    70  	var err error
    71  	txn := &themisTxn{
    72  		client:           c,
    73  		mutationCache:    newColumnMutationCache(),
    74  		oracle:           oracle,
    75  		primaryRowOffset: -1,
    76  		conf:             conf,
    77  		rpc:              newThemisRPC(c, oracle, conf),
    78  		hooks:            newHook(),
    79  	}
    80  	txn.startTs, err = txn.oracle.GetTimestamp()
    81  	if err != nil {
    82  		return nil, errors.Trace(err)
    83  	}
    84  	txn.lockCleaner = newThemisLockManager(txn.rpc, c)
    85  	return txn, nil
    86  }
    87  
    88  func (txn *themisTxn) setHook(hooks *txnHook) {
    89  	txn.hooks = hooks
    90  }
    91  
    92  func (txn *themisTxn) Gets(tbl string, gets []*hbase.Get) ([]*hbase.ResultRow, error) {
    93  	results, err := txn.rpc.themisBatchGet([]byte(tbl), gets, txn.startTs, false)
    94  	if err != nil {
    95  		return nil, errors.Trace(err)
    96  	}
    97  	var ret []*hbase.ResultRow
    98  	hasLock := false
    99  	for _, r := range results {
   100  		// if this row is locked, try clean lock and get again
   101  		if isLockResult(r) {
   102  			hasLock = true
   103  			err = txn.constructLockAndClean([]byte(tbl), r.SortedColumns)
   104  			if err != nil {
   105  				// TODO if it's a conflict error, it means this lock
   106  				// isn't expired, maybe we can retry or return partial results.
   107  				return nil, errors.Trace(err)
   108  			}
   109  		}
   110  		// it's OK, because themisBatchGet doesn't return nil value.
   111  		ret = append(ret, r)
   112  	}
   113  	if hasLock {
   114  		// after we cleaned locks, try to get again.
   115  		ret, err = txn.rpc.themisBatchGet([]byte(tbl), gets, txn.startTs, true)
   116  		if err != nil {
   117  			return nil, errors.Trace(err)
   118  		}
   119  	}
   120  	return ret, nil
   121  }
   122  
   123  func (txn *themisTxn) Get(tbl string, g *hbase.Get) (*hbase.ResultRow, error) {
   124  	r, err := txn.rpc.themisGet([]byte(tbl), g, txn.startTs, false)
   125  	if err != nil {
   126  		return nil, errors.Trace(err)
   127  	}
   128  	// contain locks, try to clean and get again
   129  	if r != nil && isLockResult(r) {
   130  		r, err = txn.tryToCleanLockAndGetAgain([]byte(tbl), g, r.SortedColumns)
   131  		if err != nil {
   132  			return nil, errors.Trace(err)
   133  		}
   134  	}
   135  	return r, nil
   136  }
   137  
   138  func (txn *themisTxn) Put(tbl string, p *hbase.Put) {
   139  	// add mutation to buffer
   140  	for _, e := range getEntriesFromPut(p) {
   141  		txn.mutationCache.addMutation([]byte(tbl), p.Row, e.Column, e.typ, e.value, false)
   142  	}
   143  }
   144  
   145  func (txn *themisTxn) Delete(tbl string, p *hbase.Delete) error {
   146  	entries, err := getEntriesFromDel(p)
   147  	if err != nil {
   148  		return errors.Trace(err)
   149  	}
   150  	for _, e := range entries {
   151  		txn.mutationCache.addMutation([]byte(tbl), p.Row, e.Column, e.typ, e.value, false)
   152  	}
   153  	return nil
   154  }
   155  
   156  func (txn *themisTxn) Commit() error {
   157  	if txn.mutationCache.getMutationCount() == 0 {
   158  		return nil
   159  	}
   160  	if txn.mutationCache.getRowCount() > txn.conf.MaxRowsInOneTxn {
   161  		return ErrTooManyRows
   162  	}
   163  
   164  	txn.selectPrimaryAndSecondaries()
   165  	err := txn.prewritePrimary()
   166  	if err != nil {
   167  		// no need to check wrong region here, hbase client will retry when
   168  		// occurs single row NotInRegion error.
   169  		log.Error(errors.ErrorStack(err))
   170  		// it's safe to retry, because this transaction is not committed.
   171  		return ErrRetryable
   172  	}
   173  
   174  	err = txn.prewriteSecondary()
   175  	if err != nil {
   176  		if isWrongRegionErr(err) {
   177  			log.Warn("region info outdated")
   178  			// reset hbase client buffered region info
   179  			txn.client.CleanAllRegionCache()
   180  		}
   181  		return ErrRetryable
   182  	}
   183  
   184  	txn.commitTs, err = txn.oracle.GetTimestamp()
   185  	if err != nil {
   186  		log.Error(errors.ErrorStack(err))
   187  		return ErrRetryable
   188  	}
   189  	err = txn.commitPrimary()
   190  	if err != nil {
   191  		// commit primary error, rollback
   192  		log.Error("commit primary row failed", txn.startTs, err)
   193  		txn.rollbackRow(txn.primaryRow.tbl, txn.primaryRow)
   194  		txn.rollbackSecondaryRow(len(txn.secondaryRows) - 1)
   195  		return ErrRetryable
   196  	}
   197  	txn.commitSecondary()
   198  	log.Debug("themis txn commit successfully", txn.startTs, txn.commitTs)
   199  	return nil
   200  }
   201  
   202  func (txn *themisTxn) commitSecondary() {
   203  	if bypass, _, _ := txn.hooks.beforeCommitSecondary(txn, nil); !bypass {
   204  		return
   205  	}
   206  	if txn.conf.brokenCommitSecondaryTest {
   207  		txn.brokenCommitSecondary()
   208  		return
   209  	}
   210  	if txn.conf.ConcurrentPrewriteAndCommit {
   211  		txn.batchCommitSecondary(txn.conf.WaitSecondaryCommit)
   212  	} else {
   213  		txn.commitSecondarySync()
   214  	}
   215  }
   216  
   217  func (txn *themisTxn) commitSecondarySync() {
   218  	for _, r := range txn.secondaryRows {
   219  		err := txn.rpc.commitSecondaryRow(r.tbl, r.row, r.mutationList(false), txn.startTs, txn.commitTs)
   220  		if err != nil {
   221  			// fail of secondary commit will not stop the commits of next
   222  			// secondaries
   223  			log.Warning(err)
   224  		}
   225  	}
   226  }
   227  
   228  func (txn *themisTxn) batchCommitSecondary(wait bool) error {
   229  	//will batch commit all rows in a region
   230  	rsRowMap, err := txn.groupByRegion()
   231  	if err != nil {
   232  		return errors.Trace(err)
   233  	}
   234  
   235  	wg := sync.WaitGroup{}
   236  	for _, regionRowMap := range rsRowMap {
   237  		wg.Add(1)
   238  		_, firstRowM := getFirstEntity(regionRowMap)
   239  		go func(cli *themisRPC, tbl string, rMap map[string]*rowMutation, startTs, commitTs uint64) {
   240  			defer wg.Done()
   241  			err := cli.batchCommitSecondaryRows([]byte(tbl), rMap, startTs, commitTs)
   242  			if err != nil {
   243  				// fail of secondary commit will not stop the commits of next
   244  				// secondaries
   245  				if isWrongRegionErr(err) {
   246  					txn.client.CleanAllRegionCache()
   247  					log.Warn("region info outdated when committing secondary rows, don't panic")
   248  				}
   249  			}
   250  		}(txn.rpc, string(firstRowM.tbl), regionRowMap, txn.startTs, txn.commitTs)
   251  	}
   252  	if wait {
   253  		wg.Wait()
   254  	}
   255  	return nil
   256  }
   257  
   258  func (txn *themisTxn) groupByRegion() (map[string]map[string]*rowMutation, error) {
   259  	rsRowMap := make(map[string]map[string]*rowMutation)
   260  	for _, rm := range txn.secondaryRows {
   261  		region, err := txn.client.LocateRegion(rm.tbl, rm.row, true)
   262  		if err != nil {
   263  			return nil, errors.Trace(err)
   264  		}
   265  		key := getBatchGroupKey(region, string(rm.tbl))
   266  		if _, exists := rsRowMap[key]; !exists {
   267  			rsRowMap[key] = map[string]*rowMutation{}
   268  		}
   269  		rsRowMap[key][string(rm.row)] = rm
   270  	}
   271  	return rsRowMap, nil
   272  }
   273  
   274  func (txn *themisTxn) commitPrimary() error {
   275  	if txn.conf.brokenCommitPrimaryTest {
   276  		return txn.brokenCommitPrimary()
   277  	}
   278  	return txn.rpc.commitRow(txn.primary.Table, txn.primary.Row,
   279  		txn.primaryRow.mutationList(false),
   280  		txn.startTs, txn.commitTs, txn.primaryRowOffset)
   281  }
   282  
   283  func (txn *themisTxn) selectPrimaryAndSecondaries() {
   284  	txn.secondary = nil
   285  	for tblName, rowMutations := range txn.mutationCache.mutations {
   286  		for _, rowMutation := range rowMutations {
   287  			row := rowMutation.row
   288  			findPrimaryInRow := false
   289  			for i, mutation := range rowMutation.mutationList(true) {
   290  				colcord := hbase.NewColumnCoordinate([]byte(tblName), row, mutation.Family, mutation.Qual)
   291  				// set the first column as primary if primary is not set by user
   292  				if txn.primaryRowOffset == -1 &&
   293  					(txn.primary == nil || txn.primary.Equal(colcord)) {
   294  					txn.primary = colcord
   295  					txn.primaryRowOffset = i
   296  					txn.primaryRow = rowMutation
   297  					findPrimaryInRow = true
   298  				} else {
   299  					txn.secondary = append(txn.secondary, colcord)
   300  				}
   301  			}
   302  			if !findPrimaryInRow {
   303  				txn.secondaryRows = append(txn.secondaryRows, rowMutation)
   304  			}
   305  		}
   306  	}
   307  
   308  	// hook for test
   309  	if bypass, _, _ := txn.hooks.afterChoosePrimaryAndSecondary(txn, nil); !bypass {
   310  		return
   311  	}
   312  
   313  	if len(txn.secondaryRows) == 0 {
   314  		txn.singleRowTxn = true
   315  	}
   316  	// construct secondary lock
   317  	secondaryLock := txn.constructSecondaryLock(hbase.TypePut)
   318  	if secondaryLock != nil {
   319  		txn.secondaryLockBytes = secondaryLock.Encode()
   320  	} else {
   321  		txn.secondaryLockBytes = nil
   322  	}
   323  }
   324  
   325  func (txn *themisTxn) constructSecondaryLock(typ hbase.Type) *themisSecondaryLock {
   326  	if txn.primaryRow.getSize() <= 1 && len(txn.secondaryRows) == 0 {
   327  		return nil
   328  	}
   329  	l := newThemisSecondaryLock()
   330  	l.primaryCoordinate = txn.primary
   331  	l.ts = txn.startTs
   332  	// TODO set client addr
   333  	return l
   334  }
   335  
   336  func (txn *themisTxn) constructPrimaryLock() *themisPrimaryLock {
   337  	l := newThemisPrimaryLock()
   338  	l.typ = txn.primaryRow.getType(txn.primary.Column)
   339  	l.ts = txn.startTs
   340  	for _, c := range txn.secondary {
   341  		l.addSecondary(c, txn.mutationCache.getMutation(c).typ)
   342  	}
   343  	return l
   344  }
   345  
   346  func (txn *themisTxn) constructLockAndClean(tbl []byte, lockKvs []*hbase.Kv) error {
   347  	locks, err := getLocksFromResults([]byte(tbl), lockKvs, txn.rpc)
   348  	if err != nil {
   349  		return errors.Trace(err)
   350  	}
   351  	for _, lock := range locks {
   352  		err := txn.cleanLockWithRetry(lock)
   353  		if err != nil {
   354  			return errors.Trace(err)
   355  		}
   356  	}
   357  	return nil
   358  }
   359  
   360  func (txn *themisTxn) tryToCleanLockAndGetAgain(tbl []byte, g *hbase.Get, lockKvs []*hbase.Kv) (*hbase.ResultRow, error) {
   361  	// try to clean locks
   362  	err := txn.constructLockAndClean(tbl, lockKvs)
   363  	if err != nil {
   364  		return nil, errors.Trace(err)
   365  	}
   366  	// get again, ignore lock
   367  	r, err := txn.rpc.themisGet([]byte(tbl), g, txn.startTs, true)
   368  	if err != nil {
   369  		return nil, errors.Trace(err)
   370  	}
   371  	return r, nil
   372  }
   373  
   374  func (txn *themisTxn) commitSecondaryAndCleanLock(lock *themisSecondaryLock, commitTs uint64) error {
   375  	cc := lock.Coordinate()
   376  	mutation := &columnMutation{
   377  		Column: &cc.Column,
   378  		mutationValuePair: &mutationValuePair{
   379  			typ: lock.typ,
   380  		},
   381  	}
   382  	err := txn.rpc.commitSecondaryRow(cc.Table, cc.Row,
   383  		[]*columnMutation{mutation}, lock.Timestamp(), commitTs)
   384  	if err != nil {
   385  		return errors.Trace(err)
   386  	}
   387  	return nil
   388  }
   389  
   390  func (txn *themisTxn) cleanLockWithRetry(lock Lock) error {
   391  	for i := 0; i < maxCleanLockRetryCount; i++ {
   392  		if exists, err := txn.lockCleaner.IsLockExists(lock.Coordinate(), 0, lock.Timestamp()); err != nil || !exists {
   393  			return errors.Trace(err)
   394  		}
   395  		log.Warnf("lock exists txn: %v lock-txn: %v row: %q", txn.startTs, lock.Timestamp(), lock.Coordinate().Row)
   396  		// try clean lock
   397  		err := txn.tryToCleanLock(lock)
   398  		if errorEqual(err, ErrLockNotExpired) {
   399  			log.Warn("sleep a while, and retry clean lock", txn.startTs)
   400  			// TODO(dongxu) use cleverer retry sleep time interval
   401  			time.Sleep(pauseTime)
   402  			continue
   403  		} else if err != nil {
   404  			return errors.Trace(err)
   405  		}
   406  		// lock cleaned successfully
   407  		return nil
   408  	}
   409  	return ErrCleanLockFailed
   410  }
   411  
   412  func (txn *themisTxn) tryToCleanLock(lock Lock) error {
   413  	// if it's secondary lock, first we'll check if its primary lock has been released.
   414  	if lock.Role() == RoleSecondary {
   415  		// get primary lock
   416  		pl := lock.Primary()
   417  		// check primary lock is exists
   418  		exists, err := txn.lockCleaner.IsLockExists(pl.Coordinate(), 0, pl.Timestamp())
   419  		if err != nil {
   420  			return errors.Trace(err)
   421  		}
   422  		if !exists {
   423  			// primary row is committed, commit this row
   424  			cc := pl.Coordinate()
   425  			commitTs, err := txn.lockCleaner.GetCommitTimestamp(cc, pl.Timestamp())
   426  			if err != nil {
   427  				return errors.Trace(err)
   428  			}
   429  			if commitTs > 0 {
   430  				// if this transction has been committed
   431  				log.Info("txn has been committed, ts:", commitTs, "prewriteTs:", pl.Timestamp())
   432  				// commit secondary rows
   433  				err := txn.commitSecondaryAndCleanLock(lock.(*themisSecondaryLock), commitTs)
   434  				if err != nil {
   435  					return errors.Trace(err)
   436  				}
   437  				return nil
   438  			}
   439  		}
   440  	}
   441  	expired, err := txn.rpc.checkAndSetLockIsExpired(lock)
   442  	if err != nil {
   443  		return errors.Trace(err)
   444  	}
   445  	// only clean expired lock
   446  	if expired {
   447  		// try to clean primary lock
   448  		pl := lock.Primary()
   449  		commitTs, cleanedLock, err := txn.lockCleaner.CleanLock(pl.Coordinate(), pl.Timestamp())
   450  		if err != nil {
   451  			return errors.Trace(err)
   452  		}
   453  		if cleanedLock != nil {
   454  			pl = cleanedLock
   455  		}
   456  		log.Info("try clean secondary locks", pl.Timestamp())
   457  		// clean secondary locks
   458  		// erase lock and data if commitTs is 0; otherwise, commit it.
   459  		for k, v := range pl.(*themisPrimaryLock).secondaries {
   460  			cc := &hbase.ColumnCoordinate{}
   461  			if err = cc.ParseFromString(k); err != nil {
   462  				return errors.Trace(err)
   463  			}
   464  			if commitTs == 0 {
   465  				// commitTs == 0, means clean primary lock successfully
   466  				// expire trx havn't committed yet, we must delete lock and
   467  				// dirty data
   468  				err = txn.lockCleaner.EraseLockAndData(cc, pl.Timestamp())
   469  				if err != nil {
   470  					return errors.Trace(err)
   471  				}
   472  			} else {
   473  				// primary row is committed, so we must commit other
   474  				// secondary rows
   475  				mutation := &columnMutation{
   476  					Column: &cc.Column,
   477  					mutationValuePair: &mutationValuePair{
   478  						typ: v,
   479  					},
   480  				}
   481  				err = txn.rpc.commitSecondaryRow(cc.Table, cc.Row,
   482  					[]*columnMutation{mutation}, pl.Timestamp(), commitTs)
   483  				if err != nil {
   484  					return errors.Trace(err)
   485  				}
   486  			}
   487  		}
   488  	} else {
   489  		return ErrLockNotExpired
   490  	}
   491  	return nil
   492  }
   493  
   494  func (txn *themisTxn) batchPrewriteSecondaryRowsWithLockClean(tbl []byte, rowMs map[string]*rowMutation) error {
   495  	locks, err := txn.batchPrewriteSecondaryRows(tbl, rowMs)
   496  	if err != nil {
   497  		return errors.Trace(err)
   498  	}
   499  
   500  	// lock clean
   501  	if locks != nil && len(locks) > 0 {
   502  		// hook for test
   503  		if bypass, _, err := txn.hooks.onSecondaryOccursLock(txn, locks); !bypass {
   504  			return errors.Trace(err)
   505  		}
   506  		// try one more time after clean lock successfully
   507  		for _, lock := range locks {
   508  			err = txn.cleanLockWithRetry(lock)
   509  			if err != nil {
   510  				return errors.Trace(err)
   511  			}
   512  
   513  			// prewrite all secondary rows
   514  			locks, err = txn.batchPrewriteSecondaryRows(tbl, rowMs)
   515  			if err != nil {
   516  				return errors.Trace(err)
   517  			}
   518  			if len(locks) > 0 {
   519  				for _, l := range locks {
   520  					log.Errorf("can't clean lock, column:%q; conflict lock: %+v, lock ts: %d", l.Coordinate(), l, l.Timestamp())
   521  				}
   522  				return ErrRetryable
   523  			}
   524  		}
   525  	}
   526  	return nil
   527  }
   528  
   529  func (txn *themisTxn) prewriteRowWithLockClean(tbl []byte, mutation *rowMutation, containPrimary bool) error {
   530  	lock, err := txn.prewriteRow(tbl, mutation, containPrimary)
   531  	if err != nil {
   532  		return errors.Trace(err)
   533  	}
   534  	// lock clean
   535  	if lock != nil {
   536  		// hook for test
   537  		if bypass, _, err := txn.hooks.beforePrewriteLockClean(txn, lock); !bypass {
   538  			return errors.Trace(err)
   539  		}
   540  		err = txn.cleanLockWithRetry(lock)
   541  		if err != nil {
   542  			return errors.Trace(err)
   543  		}
   544  		// try one more time after clean lock successfully
   545  		lock, err = txn.prewriteRow(tbl, mutation, containPrimary)
   546  		if err != nil {
   547  			return errors.Trace(err)
   548  		}
   549  		if lock != nil {
   550  			log.Errorf("can't clean lock, column:%q; conflict lock: %+v, lock ts: %d", lock.Coordinate(), lock, lock.Timestamp())
   551  			return ErrRetryable
   552  		}
   553  	}
   554  	return nil
   555  }
   556  
   557  func (txn *themisTxn) batchPrewriteSecondaryRows(tbl []byte, rowMs map[string]*rowMutation) (map[string]Lock, error) {
   558  	return txn.rpc.batchPrewriteSecondaryRows(tbl, rowMs, txn.startTs, txn.secondaryLockBytes)
   559  }
   560  
   561  func (txn *themisTxn) prewriteRow(tbl []byte, mutation *rowMutation, containPrimary bool) (Lock, error) {
   562  	// hook for test
   563  	if bypass, ret, err := txn.hooks.onPrewriteRow(txn, []interface{}{mutation, containPrimary}); !bypass {
   564  		return ret.(Lock), errors.Trace(err)
   565  	}
   566  	if containPrimary {
   567  		// try to get lock
   568  		return txn.rpc.prewriteRow(tbl, mutation.row,
   569  			mutation.mutationList(true),
   570  			txn.startTs,
   571  			txn.constructPrimaryLock().Encode(),
   572  			txn.secondaryLockBytes, txn.primaryRowOffset)
   573  	}
   574  	return txn.rpc.prewriteSecondaryRow(tbl, mutation.row,
   575  		mutation.mutationList(true),
   576  		txn.startTs,
   577  		txn.secondaryLockBytes)
   578  }
   579  
   580  func (txn *themisTxn) prewritePrimary() error {
   581  	// hook for test
   582  	if bypass, _, err := txn.hooks.beforePrewritePrimary(txn, nil); !bypass {
   583  		return err
   584  	}
   585  	err := txn.prewriteRowWithLockClean(txn.primary.Table, txn.primaryRow, true)
   586  	if err != nil {
   587  		log.Debugf("prewrite primary %v %q failed: %v", txn.startTs, txn.primaryRow.row, err.Error())
   588  		return errors.Trace(err)
   589  	}
   590  	log.Debugf("prewrite primary %v %q successfully", txn.startTs, txn.primaryRow.row)
   591  	return nil
   592  }
   593  
   594  func (txn *themisTxn) prewriteSecondary() error {
   595  	// hook for test
   596  	if bypass, _, err := txn.hooks.beforePrewriteSecondary(txn, nil); !bypass {
   597  		return err
   598  	}
   599  	if txn.conf.brokenPrewriteSecondaryTest {
   600  		return txn.brokenPrewriteSecondary()
   601  	}
   602  	if txn.conf.ConcurrentPrewriteAndCommit {
   603  		return txn.batchPrewriteSecondaries()
   604  	}
   605  	return txn.prewriteSecondarySync()
   606  }
   607  
   608  func (txn *themisTxn) prewriteSecondarySync() error {
   609  	for i, mu := range txn.secondaryRows {
   610  		err := txn.prewriteRowWithLockClean(mu.tbl, mu, false)
   611  		if err != nil {
   612  			// rollback
   613  			txn.rollbackRow(txn.primaryRow.tbl, txn.primaryRow)
   614  			txn.rollbackSecondaryRow(i)
   615  			return errors.Trace(err)
   616  		}
   617  	}
   618  	return nil
   619  }
   620  
   621  // just for test
   622  func (txn *themisTxn) brokenCommitPrimary() error {
   623  	// do nothing
   624  	log.Warn("Simulating primary commit failed")
   625  	return nil
   626  }
   627  
   628  // just for test
   629  func (txn *themisTxn) brokenCommitSecondary() {
   630  	// do nothing
   631  	log.Warn("Simulating secondary commit failed")
   632  }
   633  
   634  func (txn *themisTxn) brokenPrewriteSecondary() error {
   635  	log.Warn("Simulating prewrite secondary failed")
   636  	for i, rm := range txn.secondaryRows {
   637  		if i == len(txn.secondary)-1 {
   638  			if !txn.conf.brokenPrewriteSecondaryAndRollbackTest {
   639  				// simulating prewrite failed, need rollback
   640  				txn.rollbackRow(txn.primaryRow.tbl, txn.primaryRow)
   641  				txn.rollbackSecondaryRow(i)
   642  			}
   643  			// maybe rollback occurs error too
   644  			return ErrSimulated
   645  		}
   646  		txn.prewriteRowWithLockClean(rm.tbl, rm, false)
   647  	}
   648  	return nil
   649  }
   650  
   651  func (txn *themisTxn) batchPrewriteSecondaries() error {
   652  	wg := sync.WaitGroup{}
   653  	//will batch prewrite all rows in a region
   654  	rsRowMap, err := txn.groupByRegion()
   655  	if err != nil {
   656  		return errors.Trace(err)
   657  	}
   658  
   659  	errChan := make(chan error, len(rsRowMap))
   660  	defer close(errChan)
   661  	successChan := make(chan map[string]*rowMutation, len(rsRowMap))
   662  	defer close(successChan)
   663  
   664  	for _, regionRowMap := range rsRowMap {
   665  		wg.Add(1)
   666  		_, firstRowM := getFirstEntity(regionRowMap)
   667  		go func(tbl []byte, rMap map[string]*rowMutation) {
   668  			defer wg.Done()
   669  			err := txn.batchPrewriteSecondaryRowsWithLockClean(tbl, rMap)
   670  			if err != nil {
   671  				errChan <- err
   672  			} else {
   673  				successChan <- rMap
   674  			}
   675  		}(firstRowM.tbl, regionRowMap)
   676  	}
   677  	wg.Wait()
   678  
   679  	if len(errChan) != 0 {
   680  		// occur error, clean success prewrite mutations
   681  		log.Warnf("batch prewrite secondary rows error, rolling back %d %d", len(successChan), txn.startTs)
   682  		txn.rollbackRow(txn.primaryRow.tbl, txn.primaryRow)
   683  	L:
   684  		for {
   685  			select {
   686  			case succMutMap := <-successChan:
   687  				{
   688  					for _, rowMut := range succMutMap {
   689  						txn.rollbackRow(rowMut.tbl, rowMut)
   690  					}
   691  				}
   692  			default:
   693  				break L
   694  			}
   695  		}
   696  
   697  		err := <-errChan
   698  		if err != nil {
   699  			log.Error("batch prewrite secondary rows error, txn:", txn.startTs, err)
   700  		}
   701  		return errors.Trace(err)
   702  	}
   703  	return nil
   704  }
   705  
   706  func getFirstEntity(rowMap map[string]*rowMutation) (string, *rowMutation) {
   707  	for row, rowM := range rowMap {
   708  		return row, rowM
   709  	}
   710  	return "", nil
   711  }
   712  
   713  func getBatchGroupKey(rInfo *hbase.RegionInfo, tblName string) string {
   714  	return rInfo.Server + "_" + rInfo.Name
   715  }
   716  
   717  func (txn *themisTxn) rollbackRow(tbl []byte, mutation *rowMutation) error {
   718  	l := fmt.Sprintf("\nrolling back %q %d {\n", mutation.row, txn.startTs)
   719  	for _, v := range mutation.getColumns() {
   720  		l += fmt.Sprintf("\t%s:%s\n", string(v.Family), string(v.Qual))
   721  	}
   722  	l += "}\n"
   723  	log.Warn(l)
   724  	for _, col := range mutation.getColumns() {
   725  		cc := &hbase.ColumnCoordinate{
   726  			Table:  tbl,
   727  			Row:    mutation.row,
   728  			Column: col,
   729  		}
   730  		err := txn.lockCleaner.EraseLockAndData(cc, txn.startTs)
   731  		if err != nil {
   732  			return errors.Trace(err)
   733  		}
   734  	}
   735  	return nil
   736  }
   737  
   738  func (txn *themisTxn) rollbackSecondaryRow(successIndex int) error {
   739  	for i := successIndex; i >= 0; i-- {
   740  		r := txn.secondaryRows[i]
   741  		err := txn.rollbackRow(r.tbl, r)
   742  		if err != nil {
   743  			return errors.Trace(err)
   744  		}
   745  	}
   746  	return nil
   747  }
   748  
   749  func (txn *themisTxn) GetScanner(tbl []byte, startKey, endKey []byte, batchSize int) *ThemisScanner {
   750  	scanner := newThemisScanner(tbl, txn, batchSize, txn.client)
   751  	if startKey != nil {
   752  		scanner.setStartRow(startKey)
   753  	}
   754  	if endKey != nil {
   755  		scanner.setStopRow(endKey)
   756  	}
   757  	return scanner
   758  }
   759  
   760  func (txn *themisTxn) Release() {
   761  	txn.primary = nil
   762  	txn.primaryRow = nil
   763  	txn.secondary = nil
   764  	txn.secondaryRows = nil
   765  	txn.startTs = 0
   766  	txn.commitTs = 0
   767  }
   768  
   769  func (txn *themisTxn) String() string {
   770  	return fmt.Sprintf("%d", txn.startTs)
   771  }
   772  
   773  func (txn *themisTxn) GetCommitTS() uint64 {
   774  	return txn.commitTs
   775  }
   776  
   777  func (txn *themisTxn) GetStartTS() uint64 {
   778  	return txn.startTs
   779  }
   780  
   781  func (txn *themisTxn) LockRow(tbl string, rowkey []byte) error {
   782  	g := hbase.NewGet(rowkey)
   783  	r, err := txn.Get(tbl, g)
   784  	if err != nil {
   785  		log.Warnf("get row error, table:%s, row:%q, error:%v", tbl, rowkey, err)
   786  		return errors.Trace(err)
   787  	}
   788  	if r == nil {
   789  		log.Warnf("has not data to lock, table:%s, row:%q", tbl, rowkey)
   790  		return nil
   791  	}
   792  	for _, v := range r.Columns {
   793  		txn.mutationCache.addMutation([]byte(tbl), rowkey, &v.Column, hbase.TypeMinimum, nil, true)
   794  	}
   795  	return nil
   796  }