github.com/matrixorigin/matrixone@v1.2.0/pkg/txn/storage/memorystorage/memorytable/transaction.go (about)

     1  // Copyright 2022 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package memorytable
    16  
    17  import (
    18  	"fmt"
    19  	"sync"
    20  	"sync/atomic"
    21  
    22  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    23  )
    24  
    25  // Transaction represents a transaction
    26  // a transaction may contains multiple operations on multiple tables
    27  // a transaction commits atomically
    28  type Transaction struct {
    29  	State     *Atomic[TransactionState]
    30  	BeginTime Time
    31  	tables    transactionTableMap
    32  }
    33  
    34  type transactionTableMap struct {
    35  	sync.Mutex
    36  	Map map[int64]*transactionTable
    37  }
    38  
    39  type transactionTable struct {
    40  	table interface {
    41  		sync.Locker
    42  		commit(tx *Transaction, state any, oldState any, commitTime Time) (any, error)
    43  		rollback(oldState any)
    44  		abort(*Transaction) error
    45  	}
    46  	initState any
    47  	state     atomic.Value
    48  }
    49  
    50  // NewTransaction creates a new transaction using beginTime as snapshot time
    51  func NewTransaction(
    52  	beginTime Time,
    53  ) *Transaction {
    54  	return &Transaction{
    55  		State:     NewAtomic(Active),
    56  		BeginTime: beginTime,
    57  		tables: transactionTableMap{
    58  			Map: make(map[int64]*transactionTable),
    59  		},
    60  	}
    61  }
    62  
    63  // TransactionState represents state of a transaction
    64  type TransactionState uint8
    65  
    66  // String returns the text form of a state
    67  func (t TransactionState) String() string {
    68  	switch t {
    69  	case Active:
    70  		return "active"
    71  	case Committed:
    72  		return "committed"
    73  	case Aborted:
    74  		return "aborted"
    75  	}
    76  	panic(fmt.Sprintf("unknown state: %v", uint8(t)))
    77  }
    78  
    79  const (
    80  	// Active is the default state of a newly created transaction
    81  	Active TransactionState = iota
    82  	// Committed is the state of a committed transaction
    83  	Committed
    84  	// Aborted is the state of a aborted transaction
    85  	Aborted
    86  )
    87  
    88  // Commit commits the transaction
    89  func (t *Transaction) Commit(commitTime Time) error {
    90  	if state := t.State.Load(); state != Active {
    91  		return moerr.NewTxnNotActiveNoCtx(state.String())
    92  	}
    93  	committed := make(map[int64]any) // table id -> swapped state
    94  	t.tables.Lock()
    95  	defer t.tables.Unlock()
    96  	for id, table := range t.tables.Map {
    97  		table.table.Lock()
    98  		defer table.table.Unlock()
    99  		swapped, err := table.table.commit(t, table.state.Load(), table.initState, commitTime)
   100  		if err != nil {
   101  
   102  			// rollback previous committed
   103  			for id, swapped := range committed {
   104  				table := t.tables.Map[id]
   105  				table.table.rollback(swapped)
   106  			}
   107  
   108  			return err
   109  		}
   110  		committed[id] = swapped
   111  	}
   112  	t.State.Store(Committed)
   113  	return nil
   114  }
   115  
   116  // must call with t being locked
   117  func (t *Table[K, V, R]) commit(tx *Transaction, state any, oldState any, commitTime Time) (any, error) {
   118  
   119  	currentState := t.state.Load()
   120  	txState := state.(*tableState[K, V])
   121  	if !t.state.CompareAndSwap(oldState.(*tableState[K, V]), txState) {
   122  		newState, _, err := currentState.merge(txState)
   123  		if err != nil {
   124  			return nil, err
   125  		}
   126  		t.state.Store(newState)
   127  	}
   128  	if !t.disableHistory.Load() {
   129  		if !commitTime.IsEmpty() && len(t.history) > 0 {
   130  			last := t.history[len(t.history)-1]
   131  			if !commitTime.Greater(last.EndTime) {
   132  				return nil, moerr.NewInternalErrorNoCtx("commit time too old")
   133  			}
   134  		}
   135  		t.history = append(t.history, &history[K, V]{
   136  			EndTime:  commitTime,
   137  			EndState: currentState,
   138  		})
   139  	}
   140  
   141  	return currentState, nil
   142  }
   143  
   144  // must call with t being locked
   145  func (t *Table[K, V, R]) rollback(oldStateValue any) {
   146  	oldState := oldStateValue.(*tableState[K, V])
   147  	t.state.Store(oldState)
   148  	for len(t.history) > 0 && t.history[len(t.history)-1].EndState != oldState {
   149  		t.history = t.history[:len(t.history)-1]
   150  	}
   151  }
   152  
   153  // Abort aborts the transaction
   154  func (t *Transaction) Abort() error {
   155  	t.tables.Lock()
   156  	defer t.tables.Unlock()
   157  	for _, table := range t.tables.Map {
   158  		table.table.Lock()
   159  		defer table.table.Unlock()
   160  		if err := table.table.abort(t); err != nil {
   161  			return err
   162  		}
   163  	}
   164  	t.State.Store(Aborted)
   165  	return nil
   166  }
   167  
   168  func (t *Table[K, V, R]) abort(tx *Transaction) error {
   169  	return nil
   170  }