Feat: db reconnect
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -3,3 +3,4 @@ golias | |||||||
| gralias | gralias | ||||||
| store.json | store.json | ||||||
| config.toml | config.toml | ||||||
|  | gralias.db | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								gralias.db
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								gralias.db
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -75,6 +75,7 @@ func HandleFrontLogin(w http.ResponseWriter, r *http.Request) { | |||||||
| 		abortWithError(w, msg) | 		abortWithError(w, msg) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	var makeplayer bool | ||||||
| 	roomID := r.PostFormValue("room_id") | 	roomID := r.PostFormValue("room_id") | ||||||
| 	// make sure username does not exists | 	// make sure username does not exists | ||||||
| 	cleanName := utils.RemoveSpacesFromStr(username) | 	cleanName := utils.RemoveSpacesFromStr(username) | ||||||
| @@ -90,7 +91,9 @@ func HandleFrontLogin(w http.ResponseWriter, r *http.Request) { | |||||||
| 	// userstate, err := loadState(cleanName) | 	// userstate, err := loadState(cleanName) | ||||||
| 	userstate, err := repo.PlayerGetByName(r.Context(), cleanName) | 	userstate, err := repo.PlayerGetByName(r.Context(), cleanName) | ||||||
| 	if err != nil || userstate == nil { | 	if err != nil || userstate == nil { | ||||||
|  |  | ||||||
| 		userstate = models.InitPlayer(cleanName) | 		userstate = models.InitPlayer(cleanName) | ||||||
|  | 		makeplayer = true | ||||||
| 	} | 	} | ||||||
| 	fi := &models.FullInfo{ | 	fi := &models.FullInfo{ | ||||||
| 		State: userstate, | 		State: userstate, | ||||||
| @@ -125,11 +128,13 @@ func HandleFrontLogin(w http.ResponseWriter, r *http.Request) { | |||||||
| 		} | 		} | ||||||
| 		// save state to cache | 		// save state to cache | ||||||
| 		// if err := saveState(cleanName, userstate); err != nil { | 		// if err := saveState(cleanName, userstate); err != nil { | ||||||
| 		if err := repo.PlayerUpdate(r.Context(), userstate); err != nil { | 		if makeplayer { | ||||||
| 			// if err := saveFullInfo(r.Context(), fi); err != nil { | 			if err := repo.PlayerAdd(r.Context(), userstate); err != nil { | ||||||
| 			log.Error("failed to save state", "error", err) | 				// if err := saveFullInfo(r.Context(), fi); err != nil { | ||||||
| 			abortWithError(w, err.Error()) | 				log.Error("failed to save state", "error", err) | ||||||
| 			return | 				abortWithError(w, err.Error()) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	// if err := tmpl.ExecuteTemplate(w, "base", fi); err != nil { | 	// if err := tmpl.ExecuteTemplate(w, "base", fi); err != nil { | ||||||
|   | |||||||
| @@ -4,6 +4,8 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"log/slog" | 	"log/slog" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/jmoiron/sqlx" | 	"github.com/jmoiron/sqlx" | ||||||
| 	_ "github.com/mattn/go-sqlite3" | 	_ "github.com/mattn/go-sqlite3" | ||||||
| @@ -17,7 +19,9 @@ type AllRepos interface { | |||||||
| } | } | ||||||
|  |  | ||||||
| type RepoProvider struct { | type RepoProvider struct { | ||||||
| 	DB *sqlx.DB | 	DB       *sqlx.DB | ||||||
|  | 	mu       sync.RWMutex | ||||||
|  | 	pathToDB string | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewRepoProvider(pathToDB string) *RepoProvider { | func NewRepoProvider(pathToDB string) *RepoProvider { | ||||||
| @@ -27,14 +31,65 @@ func NewRepoProvider(pathToDB string) *RepoProvider { | |||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
| 	slog.Info("Successfully connected to database") | 	slog.Info("Successfully connected to database") | ||||||
| 	return &RepoProvider{ | 	rp := &RepoProvider{ | ||||||
| 		DB: db, | 		DB:       db, | ||||||
|  | 		pathToDB: pathToDB, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	go rp.pingLoop() | ||||||
|  |  | ||||||
|  | 	return rp | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (rp *RepoProvider) pingLoop() { | ||||||
|  | 	ticker := time.NewTicker(1 * time.Minute) | ||||||
|  | 	defer ticker.Stop() | ||||||
|  |  | ||||||
|  | 	for range ticker.C { | ||||||
|  | 		if err := rp.pingDB(); err != nil { | ||||||
|  | 			slog.Error("Database ping failed, attempting to reconnect...", "error", err) | ||||||
|  | 			rp.reconnect() | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (rp *RepoProvider) pingDB() error { | ||||||
|  | 	rp.mu.RLock() | ||||||
|  | 	defer rp.mu.RUnlock() | ||||||
|  | 	if rp.DB == nil { | ||||||
|  | 		return os.ErrClosed | ||||||
|  | 	} | ||||||
|  | 	return rp.DB.Ping() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (rp *RepoProvider) reconnect() { | ||||||
|  | 	rp.mu.Lock() | ||||||
|  | 	defer rp.mu.Unlock() | ||||||
|  |  | ||||||
|  | 	// Double-check if connection is still down | ||||||
|  | 	if rp.DB != nil { | ||||||
|  | 		if err := rp.DB.Ping(); err == nil { | ||||||
|  | 			slog.Info("Database connection already re-established.") | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		// if ping fails, we continue to reconnect | ||||||
|  | 		rp.DB.Close() // close old connection | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	slog.Info("Reconnecting to database...") | ||||||
|  | 	db, err := sqlx.Connect("sqlite3", rp.pathToDB) | ||||||
|  | 	if err != nil { | ||||||
|  | 		slog.Error("Failed to reconnect to database", "error", err) | ||||||
|  | 		rp.DB = nil // make sure DB is nil if connection failed | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	rp.DB = db | ||||||
|  | 	slog.Info("Successfully reconnected to database") | ||||||
|  | } | ||||||
|  |  | ||||||
| func getDB(ctx context.Context, db *sqlx.DB) sqlx.ExtContext { | func getDB(ctx context.Context, db *sqlx.DB) sqlx.ExtContext { | ||||||
| 	if tx, ok := ctx.Value("tx").(*sqlx.Tx); | 	if tx, ok := ctx.Value("tx").(*sqlx.Tx); ok { | ||||||
| 	ok { |  | ||||||
| 		return tx | 		return tx | ||||||
| 	} | 	} | ||||||
| 	return db | 	return db | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Grail Finder
					Grail Finder