Zero persistence
Messages vanish the moment a channel is full or a subscriber times out. No disk, no replay, no acks — the slate is always clean.
in-process · generics · Go 1.25
Lightweight, zero-allocation messaging for transient data flows — real-time streaming packets, gaming events, live signals. No persistence, no delivery guarantees, just the fastest one-way path from Publish to Subscribe.
go get github.com/F2077/go-pubsub
Built for the path where a dropped packet is fine and a stalled one is fatal.
Messages vanish the moment a channel is full or a subscriber times out. No disk, no replay, no acks — the slate is always clean.
Per-topic idle timers reset on every successful publish. Stale subscriptions close themselves and surface ErrSubscriptionTimeout.
A snapshot-then-fan-out design with a mutex per subscriber. Race-clean under -race, deadlock-free by construction.
Cap the number of topics per broker; over-cap subscribes return a wrapped ErrSubscriptionCapacityExceeded you can errors.Is.
One broker, many publishers, many subscribers, many topics. Generic over the payload T — structs, ints, strings, your type.
The fan-out recycles its subscriber snapshot through a pool — zero allocations on the hot path, even at ten thousand subscribers.
Broker, publisher, subscriber, subscribe, publish. That's the whole contract.
// 1. Create a broker.
broker, _ := pubsub.NewBroker[string]()
// 2. Bind a publisher and a subscriber to it.
publisher := pubsub.NewPublisher(broker)
subscriber := pubsub.NewSubscriber(broker)
// 3. Subscribe. WithTimeout arms a sliding timer
// that resets on every successful publish.
sub, _ := subscriber.Subscribe("alerts",
pubsub.WithChannelSize(pubsub.Medium),
pubsub.WithTimeout(200*time.Millisecond),
)
// 4. Publish synchronously. A delivery resets the timer.
publisher.Publish("alerts", "CPU over 90%!")
// 5. Receive.
select {
case msg := <-sub.Ch:
fmt.Println("Received:", msg)
case err := <-sub.ErrCh:
log.Println("Timeout:", err)
}
$ go run ./cmd/quickstart → Received: CPU over 90%!
Median of 3 runs · Intel Core Ultra 5 125H · go test -bench -benchmem
| Benchmark | ns/op | B/op | allocs/op |
|---|---|---|---|
| PublishSingleSubscriber | 107.7 | 0 | 0 |
| MultipleSubscribers (100) | 6,459 | 0 | 0 |
| MultiPub × MultiSub (5×50) | 15,996 | 304 | 12 |
| UltraLargeSinglePub (10k) | 2,017,115 | 1,028* | 0 |
| HighLoadParallel (10k, 180) | 80,579 | 3,795* | 0 |
| PublishWithTimeout | 375.2 | 248 | 3 |
* Non-zero B/op with zero allocs comes from the benchmark harness's drain goroutines, not library code.
Fire-and-forget is a sharp knife. Use it where drops are cheap and stalls are expensive.
The project ships the full set of community-health files. Play nice; they're short.