From 920e7a7acc5823d655742dbaab0c96a46925b460 Mon Sep 17 00:00:00 2001 From: Mars Hall Date: Tue, 26 Feb 2019 13:45:16 -0800 Subject: [PATCH] =?UTF-8?q?Mutex=20pg=20backend=E2=80=98s=20database=20tra?= =?UTF-8?q?nsaction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/remote-state/pg/client.go | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/backend/remote-state/pg/client.go b/backend/remote-state/pg/client.go index 2fb8d9e6f..66eedfcfa 100644 --- a/backend/remote-state/pg/client.go +++ b/backend/remote-state/pg/client.go @@ -5,6 +5,7 @@ import ( "crypto/md5" "database/sql" "fmt" + "sync" uuid "github.com/hashicorp/go-uuid" "github.com/hashicorp/terraform/state" @@ -19,13 +20,17 @@ type RemoteClient struct { SchemaName string // In-flight database transaction. Empty unless Locked. - txn *sql.Tx - info *state.LockInfo + txn *sql.Tx + txnMux sync.Mutex + info *state.LockInfo } func (c *RemoteClient) Get() (*remote.Payload, error) { query := `SELECT data FROM %s.%s WHERE name = $1` var row *sql.Row + // Take exclusive access to the database transaction + c.txnMux.Lock() + defer c.txnMux.Unlock() // Use the open transaction when present if c.txn != nil { row = c.txn.QueryRow(fmt.Sprintf(query, c.SchemaName, statesTableName), c.Name) @@ -54,6 +59,9 @@ func (c *RemoteClient) Put(data []byte) error { ON CONFLICT (name) DO UPDATE SET data = $2 WHERE %s.name = $1` var err error + // Take exclusive access to the database transaction + c.txnMux.Lock() + defer c.txnMux.Unlock() // Use the open transaction when present if c.txn != nil { _, err = c.txn.Exec(fmt.Sprintf(query, c.SchemaName, statesTableName, statesTableName), c.Name, data) @@ -69,6 +77,9 @@ func (c *RemoteClient) Put(data []byte) error { func (c *RemoteClient) Delete() error { query := `DELETE FROM %s.%s WHERE name = $1` var err error + // Take exclusive access to the database transaction + c.txnMux.Lock() + defer c.txnMux.Unlock() // Use the open transaction when present if c.txn != nil { _, err = c.txn.Exec(fmt.Sprintf(query, c.SchemaName, statesTableName), c.Name) @@ -95,6 +106,10 @@ func (c *RemoteClient) Lock(info *state.LockInfo) (string, error) { info.ID = lockID } + // Take exclusive access to the database transaction + c.txnMux.Lock() + defer c.txnMux.Unlock() + if c.txn == nil { // Most strict transaction isolation to prevent cross-talk // between incomplete state transactions. @@ -153,6 +168,9 @@ func (c *RemoteClient) getLockInfo() (*state.LockInfo, error) { } func (c *RemoteClient) Unlock(id string) error { + // Take exclusive access to the database transaction + c.txnMux.Lock() + defer c.txnMux.Unlock() if c.txn != nil { err := c.txn.Commit() if err != nil { @@ -168,6 +186,9 @@ func (c *RemoteClient) Unlock(id string) error { // transaction would not be committed (unlocked), // otherwise the transactions will leak and prevent // the process from exiting cleanly. +// +// Does not use mutex because this will implicitly be +// called from within an already mutex'd scope. func (c *RemoteClient) rollback(info *state.LockInfo) error { if c.txn != nil { err := c.txn.Rollback()