From 4697735c8da31fb0c48686e0b02634df86de1319 Mon Sep 17 00:00:00 2001
From: zeripath <art27@cantab.net>
Date: Sun, 27 Feb 2022 19:45:06 +0000
Subject: [PATCH] Adjust error for already locked db and prevent level db lock
 on malformed connstr (#18923)

This PR adjusts the error returned when there is failure to lock the level db, and
permits a connections to the same leveldb where there is a different connection string.

Reference #18921
Reference #18917

Signed-off-by: Andrew Thornton <art27@cantab.net>
---
 modules/nosql/manager_leveldb.go | 50 +++++++++++++++++++++++++++-----
 1 file changed, 43 insertions(+), 7 deletions(-)

diff --git a/modules/nosql/manager_leveldb.go b/modules/nosql/manager_leveldb.go
index eeb0cf74d9..97f917af78 100644
--- a/modules/nosql/manager_leveldb.go
+++ b/modules/nosql/manager_leveldb.go
@@ -5,10 +5,12 @@
 package nosql
 
 import (
+	"fmt"
 	"path"
 	"strconv"
 	"strings"
 
+	"code.gitea.io/gitea/modules/log"
 	"github.com/syndtr/goleveldb/leveldb"
 	"github.com/syndtr/goleveldb/leveldb/errors"
 	"github.com/syndtr/goleveldb/leveldb/opt"
@@ -20,8 +22,16 @@ func (m *Manager) CloseLevelDB(connection string) error {
 	defer m.mutex.Unlock()
 	db, ok := m.LevelDBConnections[connection]
 	if !ok {
-		connection = ToLevelDBURI(connection).String()
-		db, ok = m.LevelDBConnections[connection]
+		// Try the full URI
+		uri := ToLevelDBURI(connection)
+		db, ok = m.LevelDBConnections[uri.String()]
+
+		if !ok {
+			// Try the datadir directly
+			dataDir := path.Join(uri.Host, uri.Path)
+
+			db, ok = m.LevelDBConnections[dataDir]
+		}
 	}
 	if !ok {
 		return nil
@@ -40,6 +50,12 @@ func (m *Manager) CloseLevelDB(connection string) error {
 
 // GetLevelDB gets a levelDB for a particular connection
 func (m *Manager) GetLevelDB(connection string) (*leveldb.DB, error) {
+	// Convert the provided connection description to the common format
+	uri := ToLevelDBURI(connection)
+
+	// Get the datadir
+	dataDir := path.Join(uri.Host, uri.Path)
+
 	m.mutex.Lock()
 	defer m.mutex.Unlock()
 	db, ok := m.LevelDBConnections[connection]
@@ -48,12 +64,28 @@ func (m *Manager) GetLevelDB(connection string) (*leveldb.DB, error) {
 
 		return db.db, nil
 	}
-	uri := ToLevelDBURI(connection)
-	db = &levelDBHolder{
-		name: []string{connection, uri.String()},
+
+	db, ok = m.LevelDBConnections[uri.String()]
+	if ok {
+		db.count++
+
+		return db.db, nil
+	}
+
+	// if there is already a connection to this leveldb reuse that
+	// NOTE: if there differing options then only the first leveldb connection will be used
+	db, ok = m.LevelDBConnections[dataDir]
+	if ok {
+		db.count++
+		log.Warn("Duplicate connnection to level db: %s with different connection strings. Initial connection: %s. This connection: %s", dataDir, db.name[0], connection)
+		db.name = append(db.name, connection)
+		m.LevelDBConnections[connection] = db
+		return db.db, nil
+	}
+	db = &levelDBHolder{
+		name: []string{connection, uri.String(), dataDir},
 	}
 
-	dataDir := path.Join(uri.Host, uri.Path)
 	opts := &opt.Options{}
 	for k, v := range uri.Query() {
 		switch replacer.Replace(strings.ToLower(k)) {
@@ -134,7 +166,11 @@ func (m *Manager) GetLevelDB(connection string) (*leveldb.DB, error) {
 	db.db, err = leveldb.OpenFile(dataDir, opts)
 	if err != nil {
 		if !errors.IsCorrupted(err) {
-			return nil, err
+			if strings.Contains(err.Error(), "resource temporarily unavailable") {
+				return nil, fmt.Errorf("unable to lock level db at %s: %w", dataDir, err)
+			}
+
+			return nil, fmt.Errorf("unable to open level db at %s: %w", dataDir, err)
 		}
 		db.db, err = leveldb.RecoverFile(dataDir, opts)
 		if err != nil {