// Copyright 2016 Documize Inc. . All rights reserved. // // This software (Documize Community Edition) is licensed under // GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html // // You can operate outside the AGPL restrictions by purchasing // Documize Enterprise Edition and obtaining a commercial license // by contacting . // // https://documize.com package database import ( "crypto/rand" "time" "github.com/documize/community/core/env" "github.com/jmoiron/sqlx" ) // Lock will try to lock the database instance to the running process. // Uses a "random" delay as a por man's database cluster-aware process. // We skip delay if there are no scripts to process. func Lock(runtime *env.Runtime, scriptsToProcess int) (bool, error) { // Wait for random period of time. b := make([]byte, 2) _, err := rand.Read(b) if err != nil { return false, err } wait := ((time.Duration(b[0]) << 8) | time.Duration(b[1])) * time.Millisecond / 10 // up to 6.5 secs wait // Why delay if nothing to process? if scriptsToProcess > 0 { time.Sleep(wait) } // Start transaction fotr lock process. tx, err := runtime.Db.Beginx() if err != nil { runtime.Log.Error("Database: unable to start transaction", err) return false, err } // Lock the database. _, err = tx.Exec(runtime.StoreProvider.QueryStartLock()) if err != nil { runtime.Log.Error("Database: unable to lock tables", err) return false, err } // Unlock the database at the end of this function. defer func() { _, err = tx.Exec(runtime.StoreProvider.QueryFinishLock()) if err != nil { runtime.Log.Error("Database: unable to unlock tables", err) } tx.Commit() }() // Try to record this process as leader of database migration process. _, err = tx.Exec(runtime.StoreProvider.QueryInsertProcessID()) if err != nil { runtime.Log.Info("Database: marked as slave process awaiting upgrade") return false, nil } // We are the leader! runtime.Log.Info("Database: marked as database upgrade process leader") return true, err } // Unlock completes process that was started with Lock(). func Unlock(runtime *env.Runtime, tx *sqlx.Tx, err error, amLeader bool) error { if amLeader { defer func() { doUnlock(runtime) }() if tx != nil { if err == nil { tx.Commit() runtime.Log.Info("Database: is ready") return nil } tx.Rollback() } runtime.Log.Error("Database: install/upgrade failed", err) return err } return nil // not the leader, so ignore errors } // Helper method for defer function called from Unlock(). func doUnlock(runtime *env.Runtime) error { tx, err := runtime.Db.Beginx() if err != nil { return err } _, err = tx.Exec(runtime.StoreProvider.QueryDeleteProcessID()) if err != nil { return err } return tx.Commit() }