package bus import ( "context" "errors" "sync/atomic" ) // ErrBusClosed is returned when publishing to a closed MessageBus. var ErrBusClosed = errors.New("message bus closed") type MessageBus struct { inbound chan InboundMessage outbound chan OutboundMessage done chan struct{} closed atomic.Bool } func NewMessageBus() *MessageBus { return &MessageBus{ inbound: make(chan InboundMessage, 100), outbound: make(chan OutboundMessage, 100), done: make(chan struct{}), } } func (mb *MessageBus) PublishInbound(ctx context.Context, msg InboundMessage) error { if mb.closed.Load() { return ErrBusClosed } select { case mb.inbound <- msg: return nil case <-mb.done: return ErrBusClosed case <-ctx.Done(): return ctx.Err() } } func (mb *MessageBus) ConsumeInbound(ctx context.Context) (InboundMessage, bool) { select { case msg, ok := <-mb.inbound: return msg, ok case <-mb.done: return InboundMessage{}, false case <-ctx.Done(): return InboundMessage{}, false } } func (mb *MessageBus) PublishOutbound(ctx context.Context, msg OutboundMessage) error { if mb.closed.Load() { return ErrBusClosed } select { case mb.outbound <- msg: return nil case <-mb.done: return ErrBusClosed case <-ctx.Done(): return ctx.Err() } } func (mb *MessageBus) SubscribeOutbound(ctx context.Context) (OutboundMessage, bool) { select { case msg, ok := <-mb.outbound: return msg, ok case <-mb.done: return OutboundMessage{}, false case <-ctx.Done(): return OutboundMessage{}, false } } func (mb *MessageBus) Close() { if mb.closed.CompareAndSwap(false, true) { close(mb.done) } }