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  }