mirror of
https://github.com/j3ssie/osmedeus.git
synced 2026-09-01 04:38:47 +02:00
226 lines
6.0 KiB
Go
226 lines
6.0 KiB
Go
package core
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// ExecutionContext holds runtime state for workflow execution
|
|
type ExecutionContext struct {
|
|
WorkflowName string
|
|
WorkflowKind WorkflowKind
|
|
RunID string
|
|
Target string
|
|
WorkspacePath string
|
|
BaseFolder string
|
|
|
|
// Params are the input parameters (immutable after init)
|
|
Params map[string]interface{}
|
|
|
|
// Exports are variables exported by steps (mutable)
|
|
Exports map[string]interface{}
|
|
|
|
// Variables combines Params and Exports for template rendering
|
|
Variables map[string]interface{}
|
|
|
|
// Logger for this execution
|
|
Logger *zap.Logger
|
|
|
|
// StepIndex tracks the current step number (for display purposes)
|
|
StepIndex int
|
|
|
|
// WorkspaceName is the workspace identifier for database operations
|
|
WorkspaceName string
|
|
|
|
// mu protects concurrent access to Exports and Variables
|
|
mu sync.RWMutex
|
|
|
|
// variablesSnapshot provides O(1) read access for GetVariables()
|
|
// Updated atomically on SetVariable/SetExport/MergeExports/SetParam
|
|
variablesSnapshot atomic.Value // map[string]interface{}
|
|
}
|
|
|
|
// NewExecutionContext creates a new execution context
|
|
func NewExecutionContext(workflowName string, kind WorkflowKind, runID, target string) *ExecutionContext {
|
|
return &ExecutionContext{
|
|
WorkflowName: workflowName,
|
|
WorkflowKind: kind,
|
|
RunID: runID,
|
|
Target: target,
|
|
Params: make(map[string]interface{}),
|
|
Exports: make(map[string]interface{}),
|
|
Variables: make(map[string]interface{}),
|
|
}
|
|
}
|
|
|
|
// updateSnapshot creates an immutable copy of Variables for fast reads
|
|
// Must be called with c.mu held (Lock, not RLock)
|
|
func (c *ExecutionContext) updateSnapshot() {
|
|
snapshot := make(map[string]interface{}, len(c.Variables))
|
|
for k, v := range c.Variables {
|
|
snapshot[k] = v
|
|
}
|
|
c.variablesSnapshot.Store(snapshot)
|
|
}
|
|
|
|
// SetParam sets a parameter value
|
|
func (c *ExecutionContext) SetParam(key string, value interface{}) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.Params[key] = value
|
|
c.Variables[key] = value
|
|
c.updateSnapshot()
|
|
}
|
|
|
|
// GetParam gets a parameter value
|
|
func (c *ExecutionContext) GetParam(key string) (interface{}, bool) {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
v, ok := c.Params[key]
|
|
return v, ok
|
|
}
|
|
|
|
// SetExport sets an exported variable
|
|
func (c *ExecutionContext) SetExport(key string, value interface{}) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.Exports[key] = value
|
|
c.Variables[key] = value
|
|
c.updateSnapshot()
|
|
}
|
|
|
|
// GetExport gets an exported variable
|
|
func (c *ExecutionContext) GetExport(key string) (interface{}, bool) {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
v, ok := c.Exports[key]
|
|
return v, ok
|
|
}
|
|
|
|
// GetVariable gets a variable (param or export)
|
|
func (c *ExecutionContext) GetVariable(key string) (interface{}, bool) {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
v, ok := c.Variables[key]
|
|
return v, ok
|
|
}
|
|
|
|
// SetVariable sets a variable
|
|
func (c *ExecutionContext) SetVariable(key string, value interface{}) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.Variables[key] = value
|
|
c.updateSnapshot()
|
|
}
|
|
|
|
// GetVariables returns all variables for template rendering.
|
|
// Uses atomic snapshot for O(1) read performance.
|
|
func (c *ExecutionContext) GetVariables() map[string]interface{} {
|
|
// Fast path: return cached snapshot (no lock needed)
|
|
if snapshot := c.variablesSnapshot.Load(); snapshot != nil {
|
|
return snapshot.(map[string]interface{})
|
|
}
|
|
// Fallback for uninitialized contexts (shouldn't happen in normal use)
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
vars := make(map[string]interface{}, len(c.Variables))
|
|
for k, v := range c.Variables {
|
|
vars[k] = v
|
|
}
|
|
return vars
|
|
}
|
|
|
|
// MergeExports merges exports from a step result
|
|
func (c *ExecutionContext) MergeExports(exports map[string]interface{}) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
for k, v := range exports {
|
|
c.Exports[k] = v
|
|
c.Variables[k] = v
|
|
}
|
|
c.updateSnapshot()
|
|
}
|
|
|
|
// Clone creates a shallow copy of the context for child execution
|
|
func (c *ExecutionContext) Clone() *ExecutionContext {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
clone := &ExecutionContext{
|
|
WorkflowName: c.WorkflowName,
|
|
WorkflowKind: c.WorkflowKind,
|
|
RunID: c.RunID,
|
|
Target: c.Target,
|
|
WorkspacePath: c.WorkspacePath,
|
|
BaseFolder: c.BaseFolder,
|
|
WorkspaceName: c.WorkspaceName,
|
|
Params: make(map[string]interface{}, len(c.Params)),
|
|
Exports: make(map[string]interface{}, len(c.Exports)),
|
|
Variables: make(map[string]interface{}, len(c.Variables)),
|
|
Logger: c.Logger,
|
|
}
|
|
|
|
for k, v := range c.Params {
|
|
clone.Params[k] = v
|
|
}
|
|
for k, v := range c.Exports {
|
|
clone.Exports[k] = v
|
|
}
|
|
for k, v := range c.Variables {
|
|
clone.Variables[k] = v
|
|
}
|
|
|
|
// Initialize snapshot for fast GetVariables() reads
|
|
clone.updateSnapshot()
|
|
|
|
return clone
|
|
}
|
|
|
|
// CloneForLoop creates an optimized clone for foreach/parallel iterations.
|
|
// Key optimizations:
|
|
// - Shares Params reference (documented as immutable after init)
|
|
// - Pre-sets loop variables to avoid separate SetVariable calls
|
|
// - Reduces map copy overhead by ~33% (skips Params copy)
|
|
func (c *ExecutionContext) CloneForLoop(loopVar string, loopValue interface{}, iterID int) *ExecutionContext {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
// Estimate capacity: parent variables + 2 loop variables
|
|
varCapacity := len(c.Variables) + 2
|
|
|
|
clone := &ExecutionContext{
|
|
WorkflowName: c.WorkflowName,
|
|
WorkflowKind: c.WorkflowKind,
|
|
RunID: c.RunID,
|
|
Target: c.Target,
|
|
WorkspacePath: c.WorkspacePath,
|
|
BaseFolder: c.BaseFolder,
|
|
WorkspaceName: c.WorkspaceName,
|
|
Logger: c.Logger,
|
|
// Share immutable Params reference (no copy needed)
|
|
Params: c.Params,
|
|
// Fresh exports map for this iteration
|
|
Exports: make(map[string]interface{}, 4),
|
|
// Variables map with pre-allocated capacity
|
|
Variables: make(map[string]interface{}, varCapacity),
|
|
}
|
|
|
|
// Copy parent Variables for template rendering
|
|
for k, v := range c.Variables {
|
|
clone.Variables[k] = v
|
|
}
|
|
|
|
// Pre-set loop variables (avoids separate SetVariable calls)
|
|
if loopVar != "" {
|
|
clone.Variables[loopVar] = loopValue
|
|
}
|
|
clone.Variables["_id_"] = iterID
|
|
|
|
// Initialize snapshot for fast GetVariables() reads
|
|
clone.updateSnapshot()
|
|
|
|
return clone
|
|
}
|