github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/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  }