github.com/opentofu/opentofu@v1.7.1/internal/states/statemgr/locker.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package statemgr 7 8 import ( 9 "bytes" 10 "context" 11 "encoding/json" 12 "errors" 13 "fmt" 14 "math/rand" 15 "os" 16 "os/user" 17 "strings" 18 "text/template" 19 "time" 20 21 uuid "github.com/hashicorp/go-uuid" 22 "github.com/opentofu/opentofu/version" 23 ) 24 25 var rngSource = rand.New(rand.NewSource(time.Now().UnixNano())) 26 27 // Locker is the interface for state managers that are able to manage 28 // mutual-exclusion locks for state. 29 // 30 // Implementing Locker alongside Persistent relaxes some of the usual 31 // implementation constraints for implementations of Refresher and Persister, 32 // under the assumption that the locking mechanism effectively prevents 33 // multiple OpenTofu processes from reading and writing state concurrently. 34 // In particular, a type that implements both Locker and Persistent is only 35 // required to that the Persistent implementation is concurrency-safe within 36 // a single OpenTofu process. 37 // 38 // A Locker implementation must ensure that another processes with a 39 // similarly-configured state manager cannot successfully obtain a lock while 40 // the current process is holding it, or vice-versa, assuming that both 41 // processes agree on the locking mechanism. 42 // 43 // A Locker is not required to prevent non-cooperating processes from 44 // concurrently modifying the state, but is free to do so as an extra 45 // protection. If a mandatory locking mechanism of this sort is implemented, 46 // the state manager must ensure that RefreshState and PersistState calls 47 // can succeed if made through the same manager instance that is holding the 48 // lock, such has by retaining some sort of lock token that the Persistent 49 // methods can then use. 50 type Locker interface { 51 // Lock attempts to obtain a lock, using the given lock information. 52 // 53 // The result is an opaque id that can be passed to Unlock to release 54 // the lock, or an error if the lock cannot be acquired. Lock returns 55 // an instance of LockError immediately if the lock is already held, 56 // and the helper function LockWithContext uses this to automatically 57 // retry lock acquisition periodically until a timeout is reached. 58 Lock(info *LockInfo) (string, error) 59 60 // Unlock releases a lock previously acquired by Lock. 61 // 62 // If the lock cannot be released -- for example, if it was stolen by 63 // another user with some sort of administrative override privilege -- 64 // then an error is returned explaining the situation in a way that 65 // is suitable for returning to an end-user. 66 Unlock(id string) error 67 } 68 69 // test hook to verify that LockWithContext has attempted a lock 70 var postLockHook func() 71 72 // LockWithContext locks the given state manager using the provided context 73 // for both timeout and cancellation. 74 // 75 // This method has a built-in retry/backoff behavior up to the context's 76 // timeout. 77 func LockWithContext(ctx context.Context, s Locker, info *LockInfo) (string, error) { 78 delay := time.Second 79 maxDelay := 16 * time.Second 80 for { 81 id, err := s.Lock(info) 82 if err == nil { 83 return id, nil 84 } 85 86 le, ok := err.(*LockError) 87 if !ok { 88 // not a lock error, so we can't retry 89 return "", err 90 } 91 92 if le == nil || le.Info == nil || le.Info.ID == "" { 93 // If we don't have a complete LockError then there's something 94 // wrong with the lock. 95 return "", err 96 } 97 98 if postLockHook != nil { 99 postLockHook() 100 } 101 102 // there's an existing lock, wait and try again 103 select { 104 case <-ctx.Done(): 105 // return the last lock error with the info 106 return "", err 107 case <-time.After(delay): 108 if delay < maxDelay { 109 delay *= 2 110 } 111 } 112 } 113 } 114 115 // LockInfo stores lock metadata. 116 // 117 // Only Operation and Info are required to be set by the caller of Lock. 118 // Most callers should use NewLockInfo to create a LockInfo value with many 119 // of the fields populated with suitable default values. 120 type LockInfo struct { 121 // Unique ID for the lock. NewLockInfo provides a random ID, but this may 122 // be overridden by the lock implementation. The final value of ID will be 123 // returned by the call to Lock. 124 ID string 125 126 // OpenTofu operation, provided by the caller. 127 Operation string 128 129 // Extra information to store with the lock, provided by the caller. 130 Info string 131 132 // user@hostname when available 133 Who string 134 135 // OpenTofu version 136 Version string 137 138 // Time that the lock was taken. 139 Created time.Time 140 141 // Path to the state file when applicable. Set by the Lock implementation. 142 Path string 143 } 144 145 // NewLockInfo creates a LockInfo object and populates many of its fields 146 // with suitable default values. 147 func NewLockInfo() *LockInfo { 148 // this doesn't need to be cryptographically secure, just unique. 149 // Using math/rand alleviates the need to check handle the read error. 150 // Use a uuid format to match other IDs used throughout OpenTofu. 151 buf := make([]byte, 16) 152 rngSource.Read(buf) 153 154 id, err := uuid.FormatUUID(buf) 155 if err != nil { 156 // this of course shouldn't happen 157 panic(err) 158 } 159 160 // don't error out on user and hostname, as we don't require them 161 userName := "" 162 if userInfo, err := user.Current(); err == nil { 163 userName = userInfo.Username 164 } 165 host, _ := os.Hostname() 166 167 info := &LockInfo{ 168 ID: id, 169 Who: fmt.Sprintf("%s@%s", userName, host), 170 Version: version.Version, 171 Created: time.Now().UTC(), 172 } 173 return info 174 } 175 176 // Err returns the lock info formatted in an error 177 func (l *LockInfo) Err() error { 178 return errors.New(l.String()) 179 } 180 181 // Marshal returns a string json representation of the LockInfo 182 func (l *LockInfo) Marshal() []byte { 183 js, err := json.Marshal(l) 184 if err != nil { 185 panic(err) 186 } 187 return js 188 } 189 190 // String return a multi-line string representation of LockInfo 191 func (l *LockInfo) String() string { 192 tmpl := `Lock Info: 193 ID: {{.ID}} 194 Path: {{.Path}} 195 Operation: {{.Operation}} 196 Who: {{.Who}} 197 Version: {{.Version}} 198 Created: {{.Created}} 199 Info: {{.Info}} 200 ` 201 202 t := template.Must(template.New("LockInfo").Parse(tmpl)) 203 var out bytes.Buffer 204 if err := t.Execute(&out, l); err != nil { 205 panic(err) 206 } 207 return out.String() 208 } 209 210 // LockError is a specialization of type error that is returned by Locker.Lock 211 // to indicate that the lock is already held by another process and that 212 // retrying may be productive to take the lock once the other process releases 213 // it. 214 type LockError struct { 215 Info *LockInfo 216 Err error 217 } 218 219 func (e *LockError) Error() string { 220 var out []string 221 if e.Err != nil { 222 out = append(out, e.Err.Error()) 223 } 224 225 if e.Info != nil { 226 out = append(out, e.Info.String()) 227 } 228 return strings.Join(out, "\n") 229 }