Implemented retry logic on all transactions
This commit is contained in:
parent
6d3a28a6ce
commit
c45df4bf1a
@ -3,31 +3,47 @@ package model
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"math"
|
||||||
|
"math/rand/v2"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (db *DB) WriteArticleTags(articleID int64, tagIDs []int64) error {
|
func (db *DB) WriteArticleTags(articleID int64, tagIDs []int64) error {
|
||||||
tx, err := db.Begin()
|
for i := 0; i < TxMaxRetries; i++ {
|
||||||
if err != nil {
|
err := func() error {
|
||||||
return fmt.Errorf("error starting transaction: %v", err)
|
tx, err := db.Begin()
|
||||||
}
|
if err != nil {
|
||||||
|
return fmt.Errorf("error starting transaction: %v", err)
|
||||||
for _, tagID := range tagIDs {
|
|
||||||
query := `
|
|
||||||
INSERT INTO articles_tags (article_id, tag_id)
|
|
||||||
VALUES (?, ?)
|
|
||||||
`
|
|
||||||
if _, err := tx.Exec(query, articleID, tagID); err != nil {
|
|
||||||
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
|
||||||
log.Fatalf("error: transaction error: %v, rollback error: %v", err, rollbackErr)
|
|
||||||
}
|
}
|
||||||
return fmt.Errorf("error inserting into articles_tags: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = tx.Commit(); err != nil {
|
for _, tagID := range tagIDs {
|
||||||
return fmt.Errorf("error committing transaction: %v", err)
|
query := `
|
||||||
|
INSERT INTO articles_tags (article_id, tag_id)
|
||||||
|
VALUES (?, ?)
|
||||||
|
`
|
||||||
|
if _, err := tx.Exec(query, articleID, tagID); err != nil {
|
||||||
|
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
||||||
|
log.Fatalf("error: transaction error: %v, rollback error: %v", err, rollbackErr)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("error inserting into articles_tags: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tx.Commit(); err != nil {
|
||||||
|
return fmt.Errorf("error committing transaction: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println(err)
|
||||||
|
waitTime := time.Duration(math.Pow(2, float64(i))) * time.Second
|
||||||
|
jitter := time.Duration(rand.IntN(1000)) * time.Millisecond
|
||||||
|
time.Sleep(waitTime + jitter)
|
||||||
}
|
}
|
||||||
return nil
|
return fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) GetArticleTags(articleID int64) ([]*Tag, error) {
|
func (db *DB) GetArticleTags(articleID int64) ([]*Tag, error) {
|
||||||
|
@ -18,20 +18,16 @@ import (
|
|||||||
|
|
||||||
var TxMaxRetries = 3
|
var TxMaxRetries = 3
|
||||||
|
|
||||||
type DB struct {
|
type (
|
||||||
*sql.DB
|
DB struct{ *sql.DB }
|
||||||
}
|
Tx struct{ *sql.Tx }
|
||||||
|
Attribute struct {
|
||||||
type Tx struct {
|
Value interface{}
|
||||||
*sql.Tx
|
Table string
|
||||||
}
|
AttName string
|
||||||
|
ID int64
|
||||||
type Attribute struct {
|
}
|
||||||
Value interface{}
|
)
|
||||||
Table string
|
|
||||||
AttName string
|
|
||||||
ID int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUsername() (string, error) {
|
func getUsername() (string, error) {
|
||||||
user := os.Getenv("DB_USER")
|
user := os.Getenv("DB_USER")
|
||||||
@ -126,8 +122,8 @@ func (db *DB) UpdateAttributes(a ...*Attribute) error {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Println(err)
|
|
||||||
|
|
||||||
|
log.Println(err)
|
||||||
waitTime := time.Duration(math.Pow(2, float64(i))) * time.Second
|
waitTime := time.Duration(math.Pow(2, float64(i))) * time.Second
|
||||||
jitter := time.Duration(rand.IntN(1000)) * time.Millisecond
|
jitter := time.Duration(rand.IntN(1000)) * time.Millisecond
|
||||||
time.Sleep(waitTime + jitter)
|
time.Sleep(waitTime + jitter)
|
||||||
|
@ -3,6 +3,9 @@ package model
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"math"
|
||||||
|
"math/rand/v2"
|
||||||
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
@ -135,3 +138,61 @@ func (db *DB) GetUser(id int64) (*User, error) {
|
|||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *DB) UpdateUserAttributes(id int64, user, first, last, oldPass, newPass, newPass2 string) error {
|
||||||
|
passwordEmpty := true
|
||||||
|
if len(newPass) > 0 || len(newPass2) > 0 {
|
||||||
|
if newPass != newPass2 {
|
||||||
|
return fmt.Errorf("error: passwords do not match")
|
||||||
|
}
|
||||||
|
passwordEmpty = false
|
||||||
|
}
|
||||||
|
|
||||||
|
tx := new(Tx)
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for i := 0; i < TxMaxRetries; i++ {
|
||||||
|
err := func() error {
|
||||||
|
tx.Tx, err = db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error starting transaction: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !passwordEmpty {
|
||||||
|
if err = tx.ChangePassword(id, oldPass, newPass); err != nil {
|
||||||
|
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
||||||
|
log.Fatalf("error: transaction error: %v, rollback error: %v", err, rollbackErr)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("error changing password: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tx.UpdateAttributes(
|
||||||
|
&Attribute{Table: "users", ID: id, AttName: "username", Value: user},
|
||||||
|
&Attribute{Table: "users", ID: id, AttName: "first_name", Value: first},
|
||||||
|
&Attribute{Table: "users", ID: id, AttName: "last_name", Value: last},
|
||||||
|
); err != nil {
|
||||||
|
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
||||||
|
log.Fatalf("error: transaction error: %v, rollback error: %v", err, rollbackErr)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("error updating attributes in DB: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tx.Commit(); err != nil {
|
||||||
|
return fmt.Errorf("error committing transaction: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println(err)
|
||||||
|
waitTime := time.Duration(math.Pow(2, float64(i))) * time.Second
|
||||||
|
jitter := time.Duration(rand.IntN(1000)) * time.Millisecond
|
||||||
|
time.Sleep(waitTime + jitter)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries)
|
||||||
|
}
|
||||||
|
@ -192,47 +192,17 @@ func UpdateUser(db *model.DB, s *control.CookieStore) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tx, err := db.StartTransaction()
|
if err = db.UpdateUserAttributes(
|
||||||
if err != nil {
|
userData.ID,
|
||||||
log.Println(err)
|
userData.UserName,
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
userData.FirstName,
|
||||||
return
|
userData.LastName,
|
||||||
}
|
oldPass,
|
||||||
|
newPass,
|
||||||
if len(newPass) > 0 || len(newPass2) > 0 {
|
newPass2); err != nil {
|
||||||
if newPass != newPass2 {
|
userData.Msg = "Aktualisierung der Benutzerdaten fehlgeschlagen."
|
||||||
tx.RollbackTransaction()
|
tmpl, err := template.ParseFiles("web/templates/edit-user.html")
|
||||||
userData.Msg = "Die Passwörter stimmen nicht überein."
|
template.Must(tmpl, err).ExecuteTemplate(w, "page-content", userData)
|
||||||
tmpl, err := template.ParseFiles("web/templates/edit-user.html")
|
|
||||||
tmpl = template.Must(tmpl, err)
|
|
||||||
tmpl.ExecuteTemplate(w, "page-content", userData)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = tx.ChangePassword(userData.ID, oldPass, newPass); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
userData.Msg = "Das alte Passwort ist nicht korrekt."
|
|
||||||
tmpl, err := template.ParseFiles("web/templates/edit-user.html")
|
|
||||||
tmpl = template.Must(tmpl, err)
|
|
||||||
tmpl.ExecuteTemplate(w, "page-content", userData)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = tx.UpdateAttributes(
|
|
||||||
&model.Attribute{Table: "users", ID: userData.ID, AttName: "username", Value: userData.UserName},
|
|
||||||
&model.Attribute{Table: "users", ID: userData.ID, AttName: "first_name", Value: userData.FirstName},
|
|
||||||
&model.Attribute{Table: "users", ID: userData.ID, AttName: "last_name", Value: userData.LastName},
|
|
||||||
); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = tx.Commit(); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl, err := template.ParseFiles("web/templates/hub.html")
|
tmpl, err := template.ParseFiles("web/templates/hub.html")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user