code.gitea.io/gitea@v1.19.3/modules/nosql/manager_leveldb.go (about)

     1  // Copyright 2020 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package nosql
     5  
     6  import (
     7  	"fmt"
     8  	"path"
     9  	"runtime/pprof"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"code.gitea.io/gitea/modules/log"
    14  
    15  	"github.com/syndtr/goleveldb/leveldb"
    16  	"github.com/syndtr/goleveldb/leveldb/errors"
    17  	"github.com/syndtr/goleveldb/leveldb/opt"
    18  )
    19  
    20  // CloseLevelDB closes a levelDB
    21  func (m *Manager) CloseLevelDB(connection string) error {
    22  	m.mutex.Lock()
    23  	defer m.mutex.Unlock()
    24  	db, ok := m.LevelDBConnections[connection]
    25  	if !ok {
    26  		// Try the full URI
    27  		uri := ToLevelDBURI(connection)
    28  		db, ok = m.LevelDBConnections[uri.String()]
    29  
    30  		if !ok {
    31  			// Try the datadir directly
    32  			dataDir := path.Join(uri.Host, uri.Path)
    33  
    34  			db, ok = m.LevelDBConnections[dataDir]
    35  		}
    36  	}
    37  	if !ok {
    38  		return nil
    39  	}
    40  
    41  	db.count--
    42  	if db.count > 0 {
    43  		return nil
    44  	}
    45  
    46  	for _, name := range db.name {
    47  		delete(m.LevelDBConnections, name)
    48  	}
    49  	return db.db.Close()
    50  }
    51  
    52  // GetLevelDB gets a levelDB for a particular connection
    53  func (m *Manager) GetLevelDB(connection string) (db *leveldb.DB, err error) {
    54  	// Because we want associate any goroutines created by this call to the main nosqldb context we need to
    55  	// wrap this in a goroutine labelled with the nosqldb context
    56  	done := make(chan struct{})
    57  	var recovered interface{}
    58  	go func() {
    59  		defer func() {
    60  			recovered = recover()
    61  			if recovered != nil {
    62  				log.Critical("PANIC during GetLevelDB: %v\nStacktrace: %s", recovered, log.Stack(2))
    63  			}
    64  			close(done)
    65  		}()
    66  		pprof.SetGoroutineLabels(m.ctx)
    67  
    68  		db, err = m.getLevelDB(connection)
    69  	}()
    70  	<-done
    71  	if recovered != nil {
    72  		panic(recovered)
    73  	}
    74  	return db, err
    75  }
    76  
    77  func (m *Manager) getLevelDB(connection string) (*leveldb.DB, error) {
    78  	// Convert the provided connection description to the common format
    79  	uri := ToLevelDBURI(connection)
    80  
    81  	// Get the datadir
    82  	dataDir := path.Join(uri.Host, uri.Path)
    83  
    84  	m.mutex.Lock()
    85  	defer m.mutex.Unlock()
    86  	db, ok := m.LevelDBConnections[connection]
    87  	if ok {
    88  		db.count++
    89  
    90  		return db.db, nil
    91  	}
    92  
    93  	db, ok = m.LevelDBConnections[uri.String()]
    94  	if ok {
    95  		db.count++
    96  
    97  		return db.db, nil
    98  	}
    99  
   100  	// if there is already a connection to this leveldb reuse that
   101  	// NOTE: if there differing options then only the first leveldb connection will be used
   102  	db, ok = m.LevelDBConnections[dataDir]
   103  	if ok {
   104  		db.count++
   105  		log.Warn("Duplicate connection to level db: %s with different connection strings. Initial connection: %s. This connection: %s", dataDir, db.name[0], connection)
   106  		db.name = append(db.name, connection)
   107  		m.LevelDBConnections[connection] = db
   108  		return db.db, nil
   109  	}
   110  	db = &levelDBHolder{
   111  		name: []string{connection, uri.String(), dataDir},
   112  	}
   113  
   114  	opts := &opt.Options{}
   115  	for k, v := range uri.Query() {
   116  		switch replacer.Replace(strings.ToLower(k)) {
   117  		case "blockcachecapacity":
   118  			opts.BlockCacheCapacity, _ = strconv.Atoi(v[0])
   119  		case "blockcacheevictremoved":
   120  			opts.BlockCacheEvictRemoved, _ = strconv.ParseBool(v[0])
   121  		case "blockrestartinterval":
   122  			opts.BlockRestartInterval, _ = strconv.Atoi(v[0])
   123  		case "blocksize":
   124  			opts.BlockSize, _ = strconv.Atoi(v[0])
   125  		case "compactionexpandlimitfactor":
   126  			opts.CompactionExpandLimitFactor, _ = strconv.Atoi(v[0])
   127  		case "compactiongpoverlapsfactor":
   128  			opts.CompactionGPOverlapsFactor, _ = strconv.Atoi(v[0])
   129  		case "compactionl0trigger":
   130  			opts.CompactionL0Trigger, _ = strconv.Atoi(v[0])
   131  		case "compactionsourcelimitfactor":
   132  			opts.CompactionSourceLimitFactor, _ = strconv.Atoi(v[0])
   133  		case "compactiontablesize":
   134  			opts.CompactionTableSize, _ = strconv.Atoi(v[0])
   135  		case "compactiontablesizemultiplier":
   136  			opts.CompactionTableSizeMultiplier, _ = strconv.ParseFloat(v[0], 64)
   137  		case "compactiontablesizemultiplierperlevel":
   138  			for _, val := range v {
   139  				f, _ := strconv.ParseFloat(val, 64)
   140  				opts.CompactionTableSizeMultiplierPerLevel = append(opts.CompactionTableSizeMultiplierPerLevel, f)
   141  			}
   142  		case "compactiontotalsize":
   143  			opts.CompactionTotalSize, _ = strconv.Atoi(v[0])
   144  		case "compactiontotalsizemultiplier":
   145  			opts.CompactionTotalSizeMultiplier, _ = strconv.ParseFloat(v[0], 64)
   146  		case "compactiontotalsizemultiplierperlevel":
   147  			for _, val := range v {
   148  				f, _ := strconv.ParseFloat(val, 64)
   149  				opts.CompactionTotalSizeMultiplierPerLevel = append(opts.CompactionTotalSizeMultiplierPerLevel, f)
   150  			}
   151  		case "compression":
   152  			val, _ := strconv.Atoi(v[0])
   153  			opts.Compression = opt.Compression(val)
   154  		case "disablebufferpool":
   155  			opts.DisableBufferPool, _ = strconv.ParseBool(v[0])
   156  		case "disableblockcache":
   157  			opts.DisableBlockCache, _ = strconv.ParseBool(v[0])
   158  		case "disablecompactionbackoff":
   159  			opts.DisableCompactionBackoff, _ = strconv.ParseBool(v[0])
   160  		case "disablelargebatchtransaction":
   161  			opts.DisableLargeBatchTransaction, _ = strconv.ParseBool(v[0])
   162  		case "errorifexist":
   163  			opts.ErrorIfExist, _ = strconv.ParseBool(v[0])
   164  		case "errorifmissing":
   165  			opts.ErrorIfMissing, _ = strconv.ParseBool(v[0])
   166  		case "iteratorsamplingrate":
   167  			opts.IteratorSamplingRate, _ = strconv.Atoi(v[0])
   168  		case "nosync":
   169  			opts.NoSync, _ = strconv.ParseBool(v[0])
   170  		case "nowritemerge":
   171  			opts.NoWriteMerge, _ = strconv.ParseBool(v[0])
   172  		case "openfilescachecapacity":
   173  			opts.OpenFilesCacheCapacity, _ = strconv.Atoi(v[0])
   174  		case "readonly":
   175  			opts.ReadOnly, _ = strconv.ParseBool(v[0])
   176  		case "strict":
   177  			val, _ := strconv.Atoi(v[0])
   178  			opts.Strict = opt.Strict(val)
   179  		case "writebuffer":
   180  			opts.WriteBuffer, _ = strconv.Atoi(v[0])
   181  		case "writel0pausetrigger":
   182  			opts.WriteL0PauseTrigger, _ = strconv.Atoi(v[0])
   183  		case "writel0slowdowntrigger":
   184  			opts.WriteL0SlowdownTrigger, _ = strconv.Atoi(v[0])
   185  		case "clientname":
   186  			db.name = append(db.name, v[0])
   187  		}
   188  	}
   189  
   190  	var err error
   191  	db.db, err = leveldb.OpenFile(dataDir, opts)
   192  	if err != nil {
   193  		if !errors.IsCorrupted(err) {
   194  			if strings.Contains(err.Error(), "resource temporarily unavailable") {
   195  				err = fmt.Errorf("unable to lock level db at %s: %w", dataDir, err)
   196  				return nil, err
   197  			}
   198  
   199  			err = fmt.Errorf("unable to open level db at %s: %w", dataDir, err)
   200  			return nil, err
   201  		}
   202  		db.db, err = leveldb.RecoverFile(dataDir, opts)
   203  	}
   204  
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  
   209  	for _, name := range db.name {
   210  		m.LevelDBConnections[name] = db
   211  	}
   212  	db.count++
   213  	return db.db, nil
   214  }