package pilorama import ( "sort" "sync" "time" cidSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" "go.etcd.io/bbolt" ) type batch struct { forest *boltForest timer *time.Timer // mtx protects timer and operations fields. // Because mtx can be taken inside a transaction, // transactions MUST NOT be executed with the mutex taken to avoid a deadlock. mtx sync.Mutex start sync.Once cid cidSDK.ID treeID string results []chan<- error operations []*Move } func (b *batch) trigger() { b.mtx.Lock() if b.timer != nil { b.timer.Stop() } b.mtx.Unlock() b.start.Do(b.run) } func (b *batch) run() { fullID := bucketName(b.cid, b.treeID) err := b.forest.db.Update(func(tx *bbolt.Tx) error { bLog, bTree, err := b.forest.getTreeBuckets(tx, fullID) if err != nil { return err } b.mtx.Lock() b.timer = nil b.mtx.Unlock() // Sorting without a mutex is ok, because we append to this slice only if timer is non-nil. // See (*boltForest).addBatch for details. sort.Slice(b.operations, func(i, j int) bool { return b.operations[i].Time < b.operations[j].Time }) b.operations = removeDuplicatesInPlace(b.operations) var lm Move return b.forest.applyOperation(bLog, bTree, b.operations, &lm) }) for i := range b.results { b.results[i] <- err } } func removeDuplicatesInPlace(a []*Move) []*Move { equalCount := 0 for i := 1; i < len(a); i++ { if a[i].Time == a[i-1].Time { equalCount++ } else { a[i-equalCount] = a[i] } } return a[:len(a)-equalCount] }