package xpc import ( "context" "fmt" "reflect" "sync" ) // runtimeConfig holds runtime configuration. type runtimeConfig struct { TLSCertFile string TLSKeyFile string } // runtime defines the behavior of the XPC runtime. type runtime interface { // getInstance returns a client or instance for the given component interface. getInstance(ctx context.Context, intfType reflect.Type) (any, error) // shutdown gracefully stops the runtime and its components. shutdown(ctx context.Context) error } // newLocalRuntime creates a new local runtime. func newLocalRuntime(cfg runtimeConfig) runtime { return &localRuntime{ instances: make(map[reflect.Type]any), config: cfg, } } // newClusterRuntime creates a new cluster runtime. func newClusterRuntime(cfg runtimeConfig) runtime { return &clusterRuntime{ config: cfg, } } // localRuntime executes components in the same process. type localRuntime struct { mu sync.Mutex instances map[reflect.Type]any config runtimeConfig } func (r *localRuntime) getInstance(ctx context.Context, intfType reflect.Type) (any, error) { r.mu.Lock() if instance, ok := r.instances[intfType]; ok { r.mu.Unlock() return instance, nil } r.mu.Unlock() // Unlock before potentially long init info, ok := getComponentInfo(intfType) if !ok { return nil, fmt.Errorf("component implementation not found for interface %s", intfType.Name()) } // Double-check locking for creation r.mu.Lock() if instance, ok := r.instances[intfType]; ok { r.mu.Unlock() return instance, nil } // Create new instance implPtr := info.NewFunc() // Store in cache before injection to support circular dependencies r.instances[intfType] = implPtr r.mu.Unlock() // Perform dependency injection if err := r.inject(ctx, implPtr); err != nil { return nil, err } // Lifecycle: Init if initializer, ok := implPtr.(interface{ Init(context.Context) error }); ok { if err := initializer.Init(ctx); err != nil { return nil, fmt.Errorf("component %s Init failed: %w", info.Name, err) } } return implPtr, nil } func (r *localRuntime) inject(ctx context.Context, implPtr any) error { val := reflect.ValueOf(implPtr).Elem() typ := val.Type() for i := 0; i < val.NumField(); i++ { field := val.Field(i) fieldType := typ.Field(i) // Skip unexported fields if !fieldType.IsExported() { continue } // If field is an interface and registered as a component, inject it if field.Kind() == reflect.Interface { if _, ok := getComponentInfo(field.Type()); ok { dep, err := r.getInstance(ctx, field.Type()) if err != nil { return fmt.Errorf("failed to inject dependency %s: %w", fieldType.Name, err) } field.Set(reflect.ValueOf(dep)) } } } return nil } func (r *localRuntime) shutdown(ctx context.Context) error { r.mu.Lock() defer r.mu.Unlock() var errs []error // In local mode, we just shutdown all instantiated components. // Note: Order isn't guaranteed here. For strict ordering, we'd need a dependency graph. for _, instance := range r.instances { if closer, ok := instance.(interface{ Shutdown(context.Context) error }); ok { if err := closer.Shutdown(ctx); err != nil { errs = append(errs, err) } } } if len(errs) > 0 { return fmt.Errorf("shutdown errors: %v", errs) } return nil } // clusterRuntime executes components across a Raft cluster. type clusterRuntime struct { config runtimeConfig // raftNode *raft.Node } func (r *clusterRuntime) getInstance(ctx context.Context, intfType reflect.Type) (any, error) { // TODO: Phase 2 - Raft Integration return nil, fmt.Errorf("cluster runtime: not implemented in this phase. use XPC_MODE=local") } func (r *clusterRuntime) shutdown(ctx context.Context) error { return nil }