package comm import ( "context" "fmt" "sync" "git.yeij.top/AskaEth/Cyrene_v2/internal/bus" ) // AskHandler processes an Ask addressed to this agent. It runs on the bus's // per-subscription worker goroutine; for a single agent asks are serialized. A // handler should answer via (p *Probe).Reply or Answer; a non-nil returned // error is logged by the probe and discarded. type AskHandler func(ctx context.Context, a Ask) error // EventHandler processes an Event addressed to, or broadcast to, this agent. // Because an agent subscribes to two event patterns (broadcast and addressed), // the bus may invoke a handler concurrently from two worker goroutines, so an // EventHandler must be safe for concurrent use. type EventHandler func(ctx context.Context, e Event) error // SubscribeAsk registers h to receive every Ask addressed to this agent and // returns an unsubscribe function that removes the registration. When ctx is // canceled the registration is removed automatically; the returned function is // idempotent. func (p *Probe) SubscribeAsk(ctx context.Context, h AskHandler) (func(), error) { if h == nil { return nil, fmt.Errorf("comm: subscribe ask: handler must not be nil") } pattern := askReceivePattern(p.agentID) unsub, err := p.bus.Subscribe(ctx, pattern, func(bctx context.Context, e bus.Event) error { a, ok := e.Payload.(Ask) if !ok { return fmt.Errorf("comm: ask payload type %T, want Ask", e.Payload) } if err := h(bctx, a); err != nil { p.log.Warn("ask handler failed", "from", a.From, "ask_id", a.ID, "err", err) } return nil }) if err != nil { return nil, fmt.Errorf("comm: subscribe ask: %w", err) } return func() { unsub() }, nil } // SubscribeEvent registers h to receive both broadcast events and events // addressed to this agent. It returns an unsubscribe function that removes both // registrations; it is idempotent and safe to call more than once. When ctx is // canceled both registrations are removed automatically. // // The handler reaches the caller from two subscription worker goroutines and // may also run concurrently with a SubscribeAsk handler, so it must be safe for // concurrent use. func (p *Probe) SubscribeEvent(ctx context.Context, h EventHandler) (func(), error) { if h == nil { return nil, fmt.Errorf("comm: subscribe event: handler must not be nil") } wrap := func(bctx context.Context, e bus.Event) error { ev, ok := e.Payload.(Event) if !ok { return fmt.Errorf("comm: event payload type %T, want Event", e.Payload) } if err := h(bctx, ev); err != nil { p.log.Warn("event handler failed", "from", ev.From, "name", ev.Name, "err", err) } return nil } var ( mu sync.Mutex unsubs []func() ) combined := func() { mu.Lock() defer mu.Unlock() for _, u := range unsubs { u() } } for _, pattern := range eventReceivePatterns(p.agentID) { u, err := p.bus.Subscribe(ctx, pattern, wrap) if err != nil { combined() return nil, fmt.Errorf("comm: subscribe event %q: %w", pattern, err) } mu.Lock() unsubs = append(unsubs, u) mu.Unlock() } return combined, nil }