- [2-1] executePersistent: add defer sess.mu.Unlock() so all return paths release the session mutex. Remove explicit unlock calls. - [2-2] Protect c.sessions map access in Execute with c.mu. Replace c.Stop() in timeout/cancel path with per-session close/kill/delete so only the affected profile session is cleaned up. - [2-3] Add TestCLIExecutePersistentConsecutiveExecutes to verify the same persistent profile can be executed consecutively without blocking. Apply gofmt to all adapter files.
104 lines
2.7 KiB
Go
104 lines
2.7 KiB
Go
package adapters
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"iop/apps/node/internal/runtime"
|
|
)
|
|
|
|
// LifecycleAdapter is an optional interface for adapters that need start/stop lifecycle management.
|
|
type LifecycleAdapter interface {
|
|
Start(ctx context.Context) error
|
|
Stop(ctx context.Context) error
|
|
}
|
|
|
|
// Registry holds all registered adapters by name.
|
|
type Registry struct {
|
|
adapters map[string]runtime.Adapter
|
|
order []string // insertion order — first registered is the default
|
|
}
|
|
|
|
func NewRegistry() *Registry {
|
|
return &Registry{adapters: make(map[string]runtime.Adapter)}
|
|
}
|
|
|
|
// Register adds an adapter. The first registered adapter becomes the default.
|
|
func (r *Registry) Register(a runtime.Adapter) {
|
|
name := a.Name()
|
|
if _, exists := r.adapters[name]; !exists {
|
|
r.order = append(r.order, name)
|
|
}
|
|
r.adapters[name] = a
|
|
}
|
|
|
|
func (r *Registry) Get(name string) (runtime.Adapter, bool) {
|
|
a, ok := r.adapters[name]
|
|
return a, ok
|
|
}
|
|
|
|
func (r *Registry) Default() string {
|
|
if len(r.order) == 0 {
|
|
return ""
|
|
}
|
|
return r.order[0]
|
|
}
|
|
|
|
// All returns all registered adapters in registration order.
|
|
func (r *Registry) All() []runtime.Adapter {
|
|
out := make([]runtime.Adapter, 0, len(r.order))
|
|
for _, name := range r.order {
|
|
out = append(out, r.adapters[name])
|
|
}
|
|
return out
|
|
}
|
|
|
|
// MustGet returns the adapter or panics — useful during bootstrap validation.
|
|
func (r *Registry) MustGet(name string) runtime.Adapter {
|
|
a, ok := r.Get(name)
|
|
if !ok {
|
|
panic(fmt.Sprintf("adapter %q not registered", name))
|
|
}
|
|
return a
|
|
}
|
|
|
|
// Start calls Start on all registered LifecycleAdapters in registration order.
|
|
// On failure, already-started adapters are stopped in reverse order before returning.
|
|
func (r *Registry) Start(ctx context.Context) error {
|
|
started := make([]string, 0, len(r.order))
|
|
for _, name := range r.order {
|
|
lc, ok := r.adapters[name].(LifecycleAdapter)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if err := lc.Start(ctx); err != nil {
|
|
for i := len(started) - 1; i >= 0; i-- {
|
|
if startedLC, ok := r.adapters[started[i]].(LifecycleAdapter); ok {
|
|
_ = startedLC.Stop(context.Background())
|
|
}
|
|
}
|
|
return fmt.Errorf("adapter %q start: %w", name, err)
|
|
}
|
|
started = append(started, name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Stop calls Stop on all registered LifecycleAdapters in reverse registration order.
|
|
// All adapters are stopped even if some fail; errors are combined with errors.Join.
|
|
func (r *Registry) Stop(ctx context.Context) error {
|
|
var firstErr error
|
|
for i := len(r.order) - 1; i >= 0; i-- {
|
|
name := r.order[i]
|
|
lc, ok := r.adapters[name].(LifecycleAdapter)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if err := lc.Stop(ctx); err != nil {
|
|
if firstErr == nil {
|
|
firstErr = fmt.Errorf("adapter %q stop: %w", name, err)
|
|
}
|
|
}
|
|
}
|
|
return firstErr
|
|
}
|