github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/states/sync.go (about) 1 package states 2 3 import ( 4 "log" 5 "sync" 6 7 "github.com/eliastor/durgaform/internal/addrs" 8 "github.com/zclconf/go-cty/cty" 9 ) 10 11 // SyncState is a wrapper around State that provides concurrency-safe access to 12 // various common operations that occur during a Durgaform graph walk, or other 13 // similar concurrent contexts. 14 // 15 // When a SyncState wrapper is in use, no concurrent direct access to the 16 // underlying objects is permitted unless the caller first acquires an explicit 17 // lock, using the Lock and Unlock methods. Most callers should _not_ 18 // explicitly lock, and should instead use the other methods of this type that 19 // handle locking automatically. 20 // 21 // Since SyncState is able to safely consolidate multiple updates into a single 22 // atomic operation, many of its methods are at a higher level than those 23 // of the underlying types, and operate on the state as a whole rather than 24 // on individual sub-structures of the state. 25 // 26 // SyncState can only protect against races within its own methods. It cannot 27 // provide any guarantees about the order in which concurrent operations will 28 // be processed, so callers may still need to employ higher-level techniques 29 // for ensuring correct operation sequencing, such as building and walking 30 // a dependency graph. 31 type SyncState struct { 32 state *State 33 lock sync.RWMutex 34 } 35 36 // Module returns a snapshot of the state of the module instance with the given 37 // address, or nil if no such module is tracked. 38 // 39 // The return value is a pointer to a copy of the module state, which the 40 // caller may then freely access and mutate. However, since the module state 41 // tends to be a large data structure with many child objects, where possible 42 // callers should prefer to use a more granular accessor to access a child 43 // module directly, and thus reduce the amount of copying required. 44 func (s *SyncState) Module(addr addrs.ModuleInstance) *Module { 45 s.lock.RLock() 46 ret := s.state.Module(addr).DeepCopy() 47 s.lock.RUnlock() 48 return ret 49 } 50 51 // ModuleOutputs returns the set of OutputValues that matches the given path. 52 func (s *SyncState) ModuleOutputs(parentAddr addrs.ModuleInstance, module addrs.ModuleCall) []*OutputValue { 53 s.lock.RLock() 54 defer s.lock.RUnlock() 55 var os []*OutputValue 56 57 for _, o := range s.state.ModuleOutputs(parentAddr, module) { 58 os = append(os, o.DeepCopy()) 59 } 60 return os 61 } 62 63 // RemoveModule removes the entire state for the given module, taking with 64 // it any resources associated with the module. This should generally be 65 // called only for modules whose resources have all been destroyed, but 66 // that is not enforced by this method. 67 func (s *SyncState) RemoveModule(addr addrs.ModuleInstance) { 68 s.lock.Lock() 69 defer s.lock.Unlock() 70 71 s.state.RemoveModule(addr) 72 } 73 74 // OutputValue returns a snapshot of the state of the output value with the 75 // given address, or nil if no such output value is tracked. 76 // 77 // The return value is a pointer to a copy of the output value state, which the 78 // caller may then freely access and mutate. 79 func (s *SyncState) OutputValue(addr addrs.AbsOutputValue) *OutputValue { 80 s.lock.RLock() 81 ret := s.state.OutputValue(addr).DeepCopy() 82 s.lock.RUnlock() 83 return ret 84 } 85 86 // SetOutputValue writes a given output value into the state, overwriting 87 // any existing value of the same name. 88 // 89 // If the module containing the output is not yet tracked in state then it 90 // be added as a side-effect. 91 func (s *SyncState) SetOutputValue(addr addrs.AbsOutputValue, value cty.Value, sensitive bool) { 92 s.lock.Lock() 93 defer s.lock.Unlock() 94 95 ms := s.state.EnsureModule(addr.Module) 96 ms.SetOutputValue(addr.OutputValue.Name, value, sensitive) 97 } 98 99 // RemoveOutputValue removes the stored value for the output value with the 100 // given address. 101 // 102 // If this results in its containing module being empty, the module will be 103 // pruned from the state as a side-effect. 104 func (s *SyncState) RemoveOutputValue(addr addrs.AbsOutputValue) { 105 s.lock.Lock() 106 defer s.lock.Unlock() 107 108 ms := s.state.Module(addr.Module) 109 if ms == nil { 110 return 111 } 112 ms.RemoveOutputValue(addr.OutputValue.Name) 113 s.maybePruneModule(addr.Module) 114 } 115 116 // LocalValue returns the current value associated with the given local value 117 // address. 118 func (s *SyncState) LocalValue(addr addrs.AbsLocalValue) cty.Value { 119 s.lock.RLock() 120 // cty.Value is immutable, so we don't need any extra copying here. 121 ret := s.state.LocalValue(addr) 122 s.lock.RUnlock() 123 return ret 124 } 125 126 // SetLocalValue writes a given output value into the state, overwriting 127 // any existing value of the same name. 128 // 129 // If the module containing the local value is not yet tracked in state then it 130 // will be added as a side-effect. 131 func (s *SyncState) SetLocalValue(addr addrs.AbsLocalValue, value cty.Value) { 132 s.lock.Lock() 133 defer s.lock.Unlock() 134 135 ms := s.state.EnsureModule(addr.Module) 136 ms.SetLocalValue(addr.LocalValue.Name, value) 137 } 138 139 // RemoveLocalValue removes the stored value for the local value with the 140 // given address. 141 // 142 // If this results in its containing module being empty, the module will be 143 // pruned from the state as a side-effect. 144 func (s *SyncState) RemoveLocalValue(addr addrs.AbsLocalValue) { 145 s.lock.Lock() 146 defer s.lock.Unlock() 147 148 ms := s.state.Module(addr.Module) 149 if ms == nil { 150 return 151 } 152 ms.RemoveLocalValue(addr.LocalValue.Name) 153 s.maybePruneModule(addr.Module) 154 } 155 156 // Resource returns a snapshot of the state of the resource with the given 157 // address, or nil if no such resource is tracked. 158 // 159 // The return value is a pointer to a copy of the resource state, which the 160 // caller may then freely access and mutate. 161 func (s *SyncState) Resource(addr addrs.AbsResource) *Resource { 162 s.lock.RLock() 163 ret := s.state.Resource(addr).DeepCopy() 164 s.lock.RUnlock() 165 return ret 166 } 167 168 // ResourceInstance returns a snapshot of the state the resource instance with 169 // the given address, or nil if no such instance is tracked. 170 // 171 // The return value is a pointer to a copy of the instance state, which the 172 // caller may then freely access and mutate. 173 func (s *SyncState) ResourceInstance(addr addrs.AbsResourceInstance) *ResourceInstance { 174 s.lock.RLock() 175 ret := s.state.ResourceInstance(addr).DeepCopy() 176 s.lock.RUnlock() 177 return ret 178 } 179 180 // ResourceInstanceObject returns a snapshot of the current instance object 181 // of the given generation belonging to the instance with the given address, 182 // or nil if no such object is tracked.. 183 // 184 // The return value is a pointer to a copy of the object, which the caller may 185 // then freely access and mutate. 186 func (s *SyncState) ResourceInstanceObject(addr addrs.AbsResourceInstance, gen Generation) *ResourceInstanceObjectSrc { 187 s.lock.RLock() 188 defer s.lock.RUnlock() 189 190 inst := s.state.ResourceInstance(addr) 191 if inst == nil { 192 return nil 193 } 194 return inst.GetGeneration(gen).DeepCopy() 195 } 196 197 // SetResourceMeta updates the resource-level metadata for the resource at 198 // the given address, creating the containing module state and resource state 199 // as a side-effect if not already present. 200 func (s *SyncState) SetResourceProvider(addr addrs.AbsResource, provider addrs.AbsProviderConfig) { 201 s.lock.Lock() 202 defer s.lock.Unlock() 203 204 ms := s.state.EnsureModule(addr.Module) 205 ms.SetResourceProvider(addr.Resource, provider) 206 } 207 208 // RemoveResource removes the entire state for the given resource, taking with 209 // it any instances associated with the resource. This should generally be 210 // called only for resource objects whose instances have all been destroyed, 211 // but that is not enforced by this method. (Use RemoveResourceIfEmpty instead 212 // to safely check first.) 213 func (s *SyncState) RemoveResource(addr addrs.AbsResource) { 214 s.lock.Lock() 215 defer s.lock.Unlock() 216 217 ms := s.state.EnsureModule(addr.Module) 218 ms.RemoveResource(addr.Resource) 219 s.maybePruneModule(addr.Module) 220 } 221 222 // RemoveResourceIfEmpty is similar to RemoveResource but first checks to 223 // make sure there are no instances or objects left in the resource. 224 // 225 // Returns true if the resource was removed, or false if remaining child 226 // objects prevented its removal. Returns true also if the resource was 227 // already absent, and thus no action needed to be taken. 228 func (s *SyncState) RemoveResourceIfEmpty(addr addrs.AbsResource) bool { 229 s.lock.Lock() 230 defer s.lock.Unlock() 231 232 ms := s.state.Module(addr.Module) 233 if ms == nil { 234 return true // nothing to do 235 } 236 rs := ms.Resource(addr.Resource) 237 if rs == nil { 238 return true // nothing to do 239 } 240 if len(rs.Instances) != 0 { 241 // We don't check here for the possibility of instances that exist 242 // but don't have any objects because it's the responsibility of the 243 // instance-mutation methods to prune those away automatically. 244 return false 245 } 246 ms.RemoveResource(addr.Resource) 247 s.maybePruneModule(addr.Module) 248 return true 249 } 250 251 // SetResourceInstanceCurrent saves the given instance object as the current 252 // generation of the resource instance with the given address, simultaneously 253 // updating the recorded provider configuration address, dependencies, and 254 // resource EachMode. 255 // 256 // Any existing current instance object for the given resource is overwritten. 257 // Set obj to nil to remove the primary generation object altogether. If there 258 // are no deposed objects then the instance as a whole will be removed, which 259 // may in turn also remove the containing module if it becomes empty. 260 // 261 // The caller must ensure that the given ResourceInstanceObject is not 262 // concurrently mutated during this call, but may be freely used again once 263 // this function returns. 264 // 265 // The provider address is a resource-wide settings and is updated 266 // for all other instances of the same resource as a side-effect of this call. 267 // 268 // If the containing module for this resource or the resource itself are not 269 // already tracked in state then they will be added as a side-effect. 270 func (s *SyncState) SetResourceInstanceCurrent(addr addrs.AbsResourceInstance, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) { 271 s.lock.Lock() 272 defer s.lock.Unlock() 273 274 ms := s.state.EnsureModule(addr.Module) 275 ms.SetResourceInstanceCurrent(addr.Resource, obj.DeepCopy(), provider) 276 s.maybePruneModule(addr.Module) 277 } 278 279 // SetResourceInstanceDeposed saves the given instance object as a deposed 280 // generation of the resource instance with the given address and deposed key. 281 // 282 // Call this method only for pre-existing deposed objects that already have 283 // a known DeposedKey. For example, this method is useful if reloading objects 284 // that were persisted to a state file. To mark the current object as deposed, 285 // use DeposeResourceInstanceObject instead. 286 // 287 // The caller must ensure that the given ResourceInstanceObject is not 288 // concurrently mutated during this call, but may be freely used again once 289 // this function returns. 290 // 291 // The resource that contains the given instance must already exist in the 292 // state, or this method will panic. Use Resource to check first if its 293 // presence is not already guaranteed. 294 // 295 // Any existing current instance object for the given resource and deposed key 296 // is overwritten. Set obj to nil to remove the deposed object altogether. If 297 // the instance is left with no objects after this operation then it will 298 // be removed from its containing resource altogether. 299 // 300 // If the containing module for this resource or the resource itself are not 301 // already tracked in state then they will be added as a side-effect. 302 func (s *SyncState) SetResourceInstanceDeposed(addr addrs.AbsResourceInstance, key DeposedKey, obj *ResourceInstanceObjectSrc, provider addrs.AbsProviderConfig) { 303 s.lock.Lock() 304 defer s.lock.Unlock() 305 306 ms := s.state.EnsureModule(addr.Module) 307 ms.SetResourceInstanceDeposed(addr.Resource, key, obj.DeepCopy(), provider) 308 s.maybePruneModule(addr.Module) 309 } 310 311 // DeposeResourceInstanceObject moves the current instance object for the 312 // given resource instance address into the deposed set, leaving the instance 313 // without a current object. 314 // 315 // The return value is the newly-allocated deposed key, or NotDeposed if the 316 // given instance is already lacking a current object. 317 // 318 // If the containing module for this resource or the resource itself are not 319 // already tracked in state then there cannot be a current object for the 320 // given instance, and so NotDeposed will be returned without modifying the 321 // state at all. 322 func (s *SyncState) DeposeResourceInstanceObject(addr addrs.AbsResourceInstance) DeposedKey { 323 s.lock.Lock() 324 defer s.lock.Unlock() 325 326 ms := s.state.Module(addr.Module) 327 if ms == nil { 328 return NotDeposed 329 } 330 331 return ms.deposeResourceInstanceObject(addr.Resource, NotDeposed) 332 } 333 334 // DeposeResourceInstanceObjectForceKey is like DeposeResourceInstanceObject 335 // but uses a pre-allocated key. It's the caller's responsibility to ensure 336 // that there aren't any races to use a particular key; this method will panic 337 // if the given key is already in use. 338 func (s *SyncState) DeposeResourceInstanceObjectForceKey(addr addrs.AbsResourceInstance, forcedKey DeposedKey) { 339 s.lock.Lock() 340 defer s.lock.Unlock() 341 342 if forcedKey == NotDeposed { 343 // Usage error: should use DeposeResourceInstanceObject in this case 344 panic("DeposeResourceInstanceObjectForceKey called without forced key") 345 } 346 347 ms := s.state.Module(addr.Module) 348 if ms == nil { 349 return // Nothing to do, since there can't be any current object either. 350 } 351 352 ms.deposeResourceInstanceObject(addr.Resource, forcedKey) 353 } 354 355 // ForgetResourceInstanceAll removes the record of all objects associated with 356 // the specified resource instance, if present. If not present, this is a no-op. 357 func (s *SyncState) ForgetResourceInstanceAll(addr addrs.AbsResourceInstance) { 358 s.lock.Lock() 359 defer s.lock.Unlock() 360 361 ms := s.state.Module(addr.Module) 362 if ms == nil { 363 return 364 } 365 ms.ForgetResourceInstanceAll(addr.Resource) 366 s.maybePruneModule(addr.Module) 367 } 368 369 // ForgetResourceInstanceDeposed removes the record of the deposed object with 370 // the given address and key, if present. If not present, this is a no-op. 371 func (s *SyncState) ForgetResourceInstanceDeposed(addr addrs.AbsResourceInstance, key DeposedKey) { 372 s.lock.Lock() 373 defer s.lock.Unlock() 374 375 ms := s.state.Module(addr.Module) 376 if ms == nil { 377 return 378 } 379 ms.ForgetResourceInstanceDeposed(addr.Resource, key) 380 s.maybePruneModule(addr.Module) 381 } 382 383 // MaybeRestoreResourceInstanceDeposed will restore the deposed object with the 384 // given key on the specified resource as the current object for that instance 385 // if and only if that would not cause us to forget an existing current 386 // object for that instance. 387 // 388 // Returns true if the object was restored to current, or false if no change 389 // was made at all. 390 func (s *SyncState) MaybeRestoreResourceInstanceDeposed(addr addrs.AbsResourceInstance, key DeposedKey) bool { 391 s.lock.Lock() 392 defer s.lock.Unlock() 393 394 if key == NotDeposed { 395 panic("MaybeRestoreResourceInstanceDeposed called without DeposedKey") 396 } 397 398 ms := s.state.Module(addr.Module) 399 if ms == nil { 400 // Nothing to do, since the specified deposed object cannot exist. 401 return false 402 } 403 404 return ms.maybeRestoreResourceInstanceDeposed(addr.Resource, key) 405 } 406 407 // RemovePlannedResourceInstanceObjects removes from the state any resource 408 // instance objects that have the status ObjectPlanned, indiciating that they 409 // are just transient placeholders created during planning. 410 // 411 // Note that this does not restore any "ready" or "tainted" object that might 412 // have been present before the planned object was written. The only real use 413 // for this method is in preparing the state created during a refresh walk, 414 // where we run the planning step for certain instances just to create enough 415 // information to allow correct expression evaluation within provider and 416 // data resource blocks. Discarding planned instances in that case is okay 417 // because the refresh phase only creates planned objects to stand in for 418 // objects that don't exist yet, and thus the planned object must have been 419 // absent before by definition. 420 func (s *SyncState) RemovePlannedResourceInstanceObjects() { 421 // TODO: Merge together the refresh and plan phases into a single walk, 422 // so we can remove the need to create this "partial plan" during refresh 423 // that we then need to clean up before proceeding. 424 425 s.lock.Lock() 426 defer s.lock.Unlock() 427 428 for _, ms := range s.state.Modules { 429 moduleAddr := ms.Addr 430 431 for _, rs := range ms.Resources { 432 resAddr := rs.Addr.Resource 433 434 for ik, is := range rs.Instances { 435 instAddr := resAddr.Instance(ik) 436 437 if is.Current != nil && is.Current.Status == ObjectPlanned { 438 // Setting the current instance to nil removes it from the 439 // state altogether if there are not also deposed instances. 440 ms.SetResourceInstanceCurrent(instAddr, nil, rs.ProviderConfig) 441 } 442 443 for dk, obj := range is.Deposed { 444 // Deposed objects should never be "planned", but we'll 445 // do this anyway for the sake of completeness. 446 if obj.Status == ObjectPlanned { 447 ms.ForgetResourceInstanceDeposed(instAddr, dk) 448 } 449 } 450 } 451 } 452 453 // We may have deleted some objects, which means that we may have 454 // left a module empty, and so we must prune to preserve the invariant 455 // that only the root module is allowed to be empty. 456 s.maybePruneModule(moduleAddr) 457 } 458 } 459 460 // Lock acquires an explicit lock on the state, allowing direct read and write 461 // access to the returned state object. The caller must call Unlock once 462 // access is no longer needed, and then immediately discard the state pointer 463 // pointer. 464 // 465 // Most callers should not use this. Instead, use the concurrency-safe 466 // accessors and mutators provided directly on SyncState. 467 func (s *SyncState) Lock() *State { 468 s.lock.Lock() 469 return s.state 470 } 471 472 // Unlock releases a lock previously acquired by Lock, at which point the 473 // caller must cease all use of the state pointer that was returned. 474 // 475 // Do not call this method except to end an explicit lock acquired by 476 // Lock. If a caller calls Unlock without first holding the lock, behavior 477 // is undefined. 478 func (s *SyncState) Unlock() { 479 s.lock.Unlock() 480 } 481 482 // Close extracts the underlying state from inside this wrapper, making the 483 // wrapper invalid for any future operations. 484 func (s *SyncState) Close() *State { 485 s.lock.Lock() 486 ret := s.state 487 s.state = nil // make sure future operations can't still modify it 488 s.lock.Unlock() 489 return ret 490 } 491 492 // maybePruneModule will remove a module from the state altogether if it is 493 // empty, unless it's the root module which must always be present. 494 // 495 // This helper method is not concurrency-safe on its own, so must only be 496 // called while the caller is already holding the lock for writing. 497 func (s *SyncState) maybePruneModule(addr addrs.ModuleInstance) { 498 if addr.IsRoot() { 499 // We never prune the root. 500 return 501 } 502 503 ms := s.state.Module(addr) 504 if ms == nil { 505 return 506 } 507 508 if ms.empty() { 509 log.Printf("[TRACE] states.SyncState: pruning %s because it is empty", addr) 510 s.state.RemoveModule(addr) 511 } 512 } 513 514 func (s *SyncState) MoveAbsResource(src, dst addrs.AbsResource) { 515 s.lock.Lock() 516 defer s.lock.Unlock() 517 518 s.state.MoveAbsResource(src, dst) 519 } 520 521 func (s *SyncState) MaybeMoveAbsResource(src, dst addrs.AbsResource) bool { 522 s.lock.Lock() 523 defer s.lock.Unlock() 524 525 return s.state.MaybeMoveAbsResource(src, dst) 526 } 527 528 func (s *SyncState) MoveResourceInstance(src, dst addrs.AbsResourceInstance) { 529 s.lock.Lock() 530 defer s.lock.Unlock() 531 532 s.state.MoveAbsResourceInstance(src, dst) 533 } 534 535 func (s *SyncState) MaybeMoveResourceInstance(src, dst addrs.AbsResourceInstance) bool { 536 s.lock.Lock() 537 defer s.lock.Unlock() 538 539 return s.state.MaybeMoveAbsResourceInstance(src, dst) 540 } 541 542 func (s *SyncState) MoveModuleInstance(src, dst addrs.ModuleInstance) { 543 s.lock.Lock() 544 defer s.lock.Unlock() 545 546 s.state.MoveModuleInstance(src, dst) 547 } 548 549 func (s *SyncState) MaybeMoveModuleInstance(src, dst addrs.ModuleInstance) bool { 550 s.lock.Lock() 551 defer s.lock.Unlock() 552 553 return s.state.MaybeMoveModuleInstance(src, dst) 554 }