github.com/matrixorigin/matrixone@v1.2.0/pkg/txn/client/leak_checker.go (about) 1 // Copyright 2023 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 client 16 17 import ( 18 "bytes" 19 "context" 20 "sync" 21 "time" 22 23 "github.com/matrixorigin/matrixone/pkg/common/log" 24 "github.com/matrixorigin/matrixone/pkg/common/runtime" 25 "github.com/matrixorigin/matrixone/pkg/common/stopper" 26 "github.com/matrixorigin/matrixone/pkg/pb/txn" 27 ) 28 29 // leakChecker is used to detect leak txn which is not committed or aborted. 30 type leakChecker struct { 31 sync.RWMutex 32 logger *log.MOLogger 33 actives []ActiveTxn 34 maxActiveAges time.Duration 35 leakHandleFunc func([]ActiveTxn) 36 stopper *stopper.Stopper 37 } 38 39 func newLeakCheck( 40 maxActiveAges time.Duration, 41 leakHandleFunc func([]ActiveTxn)) *leakChecker { 42 logger := runtime.DefaultRuntime().Logger() 43 return &leakChecker{ 44 logger: logger, 45 maxActiveAges: maxActiveAges, 46 leakHandleFunc: leakHandleFunc, 47 stopper: stopper.NewStopper("txn-leak-checker", 48 stopper.WithLogger(logger.RawLogger())), 49 } 50 } 51 52 func (lc *leakChecker) start() { 53 if err := lc.stopper.RunTask(lc.check); err != nil { 54 panic(err) 55 } 56 } 57 58 func (lc *leakChecker) close() { 59 lc.stopper.Stop() 60 } 61 62 func (lc *leakChecker) txnOpened( 63 txnOp *txnOperator, 64 txnID []byte, 65 options txn.TxnOptions) { 66 lc.Lock() 67 defer lc.Unlock() 68 lc.actives = append(lc.actives, ActiveTxn{ 69 Options: options, 70 ID: txnID, 71 CreateAt: time.Now(), 72 txnOp: txnOp, 73 }) 74 } 75 76 func (lc *leakChecker) txnClosed(txnID []byte) { 77 lc.Lock() 78 defer lc.Unlock() 79 values := lc.actives[:0] 80 for idx, txn := range lc.actives { 81 if bytes.Equal(txn.ID, txnID) { 82 values = append(values, lc.actives[idx+1:]...) 83 break 84 } 85 values = append(values, txn) 86 } 87 lc.actives = values 88 } 89 90 func (lc *leakChecker) check(ctx context.Context) { 91 for { 92 select { 93 case <-ctx.Done(): 94 return 95 case <-time.After(lc.maxActiveAges): 96 if values := lc.doCheck(); len(values) > 0 { 97 lc.leakHandleFunc(values) 98 } 99 } 100 } 101 } 102 103 func (lc *leakChecker) doCheck() []ActiveTxn { 104 lc.RLock() 105 defer lc.RUnlock() 106 107 var values []ActiveTxn 108 now := time.Now() 109 for _, txn := range lc.actives { 110 if now.Sub(txn.CreateAt) >= lc.maxActiveAges { 111 if txn.txnOp != nil { 112 txn.Options.Counter = txn.txnOp.counter() 113 txn.Options.InRunSql = txn.txnOp.inRunSql() 114 txn.Options.InCommit = txn.txnOp.inCommit() 115 txn.Options.InRollback = txn.txnOp.inRollback() 116 txn.Options.SessionInfo = txn.txnOp.options.SessionInfo 117 } 118 values = append(values, txn) 119 } 120 } 121 return values 122 } 123 124 type ActiveTxn struct { 125 Options txn.TxnOptions 126 ID []byte 127 CreateAt time.Time 128 txnOp *txnOperator 129 }