matrixcloud / internal /logs /stream.go
github-actions[bot]
Deploy from 4dfd54f2
857a91b
Raw
History Blame Contribute Delete
2.11 kB
package logs
import "sync"
// Event is a single lifecycle/log event emitted by a job.
type Event struct {
Step string `json:"step"`
Status string `json:"status"`
Message string `json:"message,omitempty"`
Data map[string]any `json:"data,omitempty"`
}
// Bus is a per-job event broadcaster. It retains the full event history so a
// subscriber that connects late still receives every event, then live updates
// until the bus is closed.
type Bus struct {
mu sync.Mutex
history []Event
subs map[chan Event]struct{}
closed bool
}
// NewBus creates an empty event bus.
func NewBus() *Bus {
return &Bus{subs: make(map[chan Event]struct{})}
}
// Publish records an event and fans it out to all current subscribers.
func (b *Bus) Publish(e Event) {
b.mu.Lock()
defer b.mu.Unlock()
if b.closed {
return
}
b.history = append(b.history, e)
for ch := range b.subs {
select {
case ch <- e:
default:
// Slow consumer: drop rather than block the producer. The full
// history is still available via the initial replay.
}
}
}
// Subscribe returns the event history followed by a channel of future events.
// The channel is closed when the bus is closed. Callers must invoke the
// returned cancel function to release resources.
func (b *Bus) Subscribe() (history []Event, ch <-chan Event, cancel func()) {
b.mu.Lock()
defer b.mu.Unlock()
hist := make([]Event, len(b.history))
copy(hist, b.history)
c := make(chan Event, 64)
if b.closed {
close(c)
return hist, c, func() {}
}
b.subs[c] = struct{}{}
return hist, c, func() {
b.mu.Lock()
defer b.mu.Unlock()
if _, ok := b.subs[c]; ok {
delete(b.subs, c)
close(c)
}
}
}
// Close marks the bus closed and closes all subscriber channels. Further
// Publish calls are ignored.
func (b *Bus) Close() {
b.mu.Lock()
defer b.mu.Unlock()
if b.closed {
return
}
b.closed = true
for ch := range b.subs {
close(ch)
delete(b.subs, ch)
}
}
// Closed reports whether the bus has been closed.
func (b *Bus) Closed() bool {
b.mu.Lock()
defer b.mu.Unlock()
return b.closed
}