github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/read_state.go (about) 1 // Copyright 2019 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 // of this source code is governed by a BSD-style license that can be found in 3 // the LICENSE file. 4 5 package pebble 6 7 import "sync/atomic" 8 9 // readState encapsulates the state needed for reading (the current version and 10 // list of memtables). Loading the readState is done without grabbing 11 // DB.mu. Instead, a separate DB.readState.RWMutex is used for 12 // synchronization. This mutex solely covers the current readState object which 13 // means it is rarely or ever contended. 14 // 15 // Note that various fancy lock-free mechanisms can be imagined for loading the 16 // readState, but benchmarking showed the ones considered to purely be 17 // pessimizations. The RWMutex version is a single atomic increment for the 18 // RLock and an atomic decrement for the RUnlock. It is difficult to do better 19 // than that without something like thread-local storage which isn't available 20 // in Go. 21 type readState struct { 22 db *DB 23 refcnt atomic.Int32 24 current *version 25 memtables flushableList 26 } 27 28 // ref adds a reference to the readState. 29 func (s *readState) ref() { 30 s.refcnt.Add(1) 31 } 32 33 // unref removes a reference to the readState. If this was the last reference, 34 // the reference the readState holds on the version is released. Requires DB.mu 35 // is NOT held as version.unref() will acquire it. See unrefLocked() if DB.mu 36 // is held by the caller. 37 func (s *readState) unref() { 38 if s.refcnt.Add(-1) != 0 { 39 return 40 } 41 s.current.Unref() 42 for _, mem := range s.memtables { 43 mem.readerUnref(true) 44 } 45 46 // The last reference to the readState was released. Check to see if there 47 // are new obsolete tables to delete. 48 s.db.maybeScheduleObsoleteTableDeletion() 49 } 50 51 // unrefLocked removes a reference to the readState. If this was the last 52 // reference, the reference the readState holds on the version is 53 // released. 54 // 55 // DB.mu must be held. See unref() if DB.mu is NOT held by the caller. 56 func (s *readState) unrefLocked() { 57 if s.refcnt.Add(-1) != 0 { 58 return 59 } 60 s.current.UnrefLocked() 61 for _, mem := range s.memtables { 62 mem.readerUnrefLocked(true) 63 } 64 65 // In this code path, the caller is responsible for scheduling obsolete table 66 // deletion as necessary. 67 } 68 69 // loadReadState returns the current readState. The returned readState must be 70 // unreferenced when the caller is finished with it. 71 func (d *DB) loadReadState() *readState { 72 d.readState.RLock() 73 state := d.readState.val 74 state.ref() 75 d.readState.RUnlock() 76 return state 77 } 78 79 // updateReadStateLocked creates a new readState from the current version and 80 // list of memtables. Requires DB.mu is held. If checker is not nil, it is 81 // called after installing the new readState. 82 func (d *DB) updateReadStateLocked(checker func(*DB) error) { 83 s := &readState{ 84 db: d, 85 current: d.mu.versions.currentVersion(), 86 memtables: d.mu.mem.queue, 87 } 88 s.refcnt.Store(1) 89 s.current.Ref() 90 for _, mem := range s.memtables { 91 mem.readerRef() 92 } 93 94 d.readState.Lock() 95 old := d.readState.val 96 d.readState.val = s 97 d.readState.Unlock() 98 if checker != nil { 99 if err := checker(d); err != nil { 100 d.opts.Logger.Fatalf("checker failed with error: %s", err) 101 } 102 } 103 if old != nil { 104 old.unrefLocked() 105 } 106 }