internal/stacks/stackruntime) uses a fundamentally different execution model than the traditional modules runtime.
Architecture Philosophy
While the modules runtime builds explicit dependency graphs and walks them, the stacks runtime constructs an implicit data flow graph dynamically during evaluation.Modules Runtime vs. Stacks Runtime
- Modules Runtime
- Stacks Runtime
Characteristics:
- Explicit graph construction
- Walker-controlled concurrency
- Shared mutable
EvalContext - Static dependency analysis
Core Components
The stacks runtime is organized around several key packages underinternal/stacks/:
Package Structure
stackaddrs - Address Types
Analogous to top-leveladdrs package but for stacks:
addrs package since stacks wrap modules.
stackconfig - Configuration
Loads and parses.tfstack.hcl files:
stackruntime - Execution
The public API for executing stacks operations:internal/stacks/README.md
Object Model
The runtime uses a tree of objects rather than a flat graph:Config vs. Dynamic Objects
Each concept has two object types:Config Objects
Static configurationExample:
InputVariableConfig- Represents blocks in
.tfstack.hcl - Static validation only
- No dynamic evaluation
- Singleton per
Main
Dynamic Objects
Runtime instancesExample:
InputVariable- Evaluates expressions
- Interacts with providers
- Creates plans/applies changes
- Singleton per evaluation phase
- Config objects: Validate that references to non-existent objects don’t exist
- Dynamic objects: Perform all dynamic expression evaluation and external interaction
Calls vs. Instances
Some objects have an additional layer:component, stack, and provider:
- Config -
componentblock declaration - Call - Handles
for_eachexpansion, produces reference value - Instance - Individual instance, performs actual work
internal/stacks/stackruntime/internal/stackeval/README.md:62
Evaluation Phases
EachMain object is created for one evaluation phase:
Phase Characteristics
- ValidatePhase
- PlanPhase
- ApplyPhase
- InspectPhase
Static validation only
- No dynamic evaluation
- No provider calls
- Checks configuration validity
- Fast execution
Object Singletons
All objects reachable fromMain must be singletons:
Why singletons matter:
- Track asynchronous work in progress
- Cache results of expensive operations
- Prevent duplicate provider calls
- Ensure consistent results
- Constructor
newXxx()is unexported - Constructor called from exactly one location
- Parent object caches instance in unexported map
- Public method returns cached instance
internal/stacks/stackruntime/internal/stackeval/README.md:106
Expression Evaluation
Expression evaluation uses a two-interface pattern:EvaluationScope
Resolves references to objects:Stack- Global scope for a stackComponent- Addseach.key,each.value,selfStackCall- Adds call-specific context
Referenceable
Provides values for references:InputVariable- Returns variable valueComponent- Returns outputs (from plan or state)Provider- Returns provider configuration
Evaluation Flow
Steps:- Analyze expressions for
hcl.Traversalreferences - Parse into
stackaddrs.Referenceaddresses - Resolve addresses to
Referenceableobjects viaEvaluationScope - Get
cty.Valuefrom each viaExprReferenceValue - Build
hcl.EvalContextwith values - Evaluate expression
internal/stacks/stackruntime/internal/stackeval/README.md:194
Promise-Based Concurrency
Instead of explicit graph walks, the runtime uses promises:- Implicit dependencies: Data flow creates dependency graph automatically
- Automatic parallelism: Independent promises run concurrently
- No duplicate work: Promises execute once regardless of consumers
- Simple reasoning: No explicit graph construction
internal/promising/README.md (referenced from stacks runtime)
Checked vs. Unchecked Results
To avoid duplicate diagnostics, methods come in pairs:- Every fallible operation can return a placeholder on failure
- Only one codepath calls the
Check...variant - All other callers use the unprefixed version
Check... variants.
See: internal/stacks/stackruntime/internal/stackeval/README.md:263
Walk Operations
Walks ensure every object gets visited at least once:Static Walk
ForValidatePhase and PlanPhase:
*Config objects only.
Dynamic Walk
ForPlanPhase and ApplyPhase:
internal/stacks/stackruntime/internal/stackeval/README.md:308
Apply-Phase Scheduling
Apply phase needs explicit ordering for component changes:- During plan phase, analyze component references
- Build component dependency graph
- Include in plan
- Use during apply to schedule changes
- Components apply in dependency order
- No cycles (detected during planning)
- Maximum parallelism for independent components
- Explicit ordering for side-effects
internal/stacks/stackruntime/internal/stackeval/README.md:364
Comparison with Modules Runtime
| Aspect | Modules Runtime | Stacks Runtime |
|---|---|---|
| Dependency Graph | Explicit, pre-built | Implicit, dynamic |
| Concurrency | Walker with semaphore | Promises |
| Shared State | Mutable EvalContext | Immutable method results |
| Execution Order | Static graph edges | Dynamic data flow |
| Expansion | Sub-graph creation | Lazy instance creation |
| Scheduling | Graph walk algorithm | Promise dependencies |
| Memory | O(V+E) graph | O(V) object tree |
| Complexity | Graph algorithms | Promise resolution |
Performance Characteristics
Memory Usage
- Object tree: O(V) where V is configuration objects
- Promises: O(P) where P is promise-returning operations
- No explicit graph: Saves O(E) for edges
Concurrency
- Natural parallelism: No semaphore limit
- Data-driven: Only blocks on actual dependencies
- No artificial constraints: Unlike modules’ default parallelism=10
Lazy Evaluation
- On-demand: Only evaluates referenced values
- Caching: Promises ensure work happens once
- Short-circuit: Errors prevent downstream work
Further Reading
Modules Runtime
Compare with traditional execution model
Resource Lifecycle
How components interact with resources