|
@@ -0,0 +1,150 @@
|
|
|
|
|
+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
|
|
|
|
|
+}
|
|
|
|
|
+
|