Implemented retry logic on all transactions
This commit is contained in:
		@@ -3,9 +3,14 @@ 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 {
 | 
				
			||||||
 | 
						for i := 0; i < TxMaxRetries; i++ {
 | 
				
			||||||
 | 
							err := func() error {
 | 
				
			||||||
			tx, err := db.Begin()
 | 
								tx, err := db.Begin()
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				return fmt.Errorf("error starting transaction: %v", err)
 | 
									return fmt.Errorf("error starting transaction: %v", err)
 | 
				
			||||||
@@ -28,6 +33,17 @@ func (db *DB) WriteArticleTags(articleID int64, tagIDs []int64) error {
 | 
				
			|||||||
				return fmt.Errorf("error committing transaction: %v", err)
 | 
									return fmt.Errorf("error committing transaction: %v", err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			return nil
 | 
								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)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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 {
 | 
					 | 
				
			||||||
	*sql.Tx
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Attribute struct {
 | 
					 | 
				
			||||||
		Value   interface{}
 | 
							Value   interface{}
 | 
				
			||||||
		Table   string
 | 
							Table   string
 | 
				
			||||||
		AttName string
 | 
							AttName string
 | 
				
			||||||
		ID      int64
 | 
							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()
 | 
					 | 
				
			||||||
				userData.Msg = "Die Passwörter stimmen nicht überein."
 | 
					 | 
				
			||||||
			tmpl, err := template.ParseFiles("web/templates/edit-user.html")
 | 
								tmpl, err := template.ParseFiles("web/templates/edit-user.html")
 | 
				
			||||||
				tmpl = template.Must(tmpl, err)
 | 
								template.Must(tmpl, err).ExecuteTemplate(w, "page-content", userData)
 | 
				
			||||||
				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")
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user