GoEventBus
A blazingâfast, inâmemory, lockâfree event bus for Goâideal for lowâlatency pipelines, microservices, and game loops.

đ Table of Contents
Features
- Lockâfree ring buffer: Efficient event dispatching using atomic operations and cacheâline padding.
- Contextâaware handlers: Every handler now receives a
context.Context, enabling deadlines, cancellation and tracing.
- Backâpressure policies: Choose whether to drop, block, or error when the buffer is full.
- Configurable buffer size: Specify the ring buffer size (must be a power of two) when creating the store.
- Async or Sync processing: Toggle between synchronous handling or async goroutineâbased processing via the
Async flag.
- Metrics: Track published, processed, and errored event counts with a simple API.
- Simple API: Easy to subscribe, publish, and retrieve metrics.
Why GoEventBus?
Modern Go apps demand lightningâfast, nonâblocking communicationâbut channels can bottleneck and external brokers add latency, complexity and ops overhead. GoEventBus is your inâprocess, lockâfree solution:
- Microâlatency dispatch
Atomic, cacheâaligned ring buffers deliver subâmicrosecond handâoffsâno locks, no syscalls, zero garbage.
- Sync or Async at will
Flip a switch to run handlers inline for predictability or in goroutines for massive parallelism.
- Backâpressure your way
Choose from DropOldest, Block or ReturnError to match your systemâs tolerance for loss or latency.
- Builtâin observability
Expose counters for published, processed and errored events out of the boxâno extra instrumentation.
- Dropâin, zero deps
One import, no external services, no workers to manageâjust Go 1.21+ and youâre off.
Whether youâre building realâtime analytics, highâthroughput microservices, or game engines, GoEventBus keeps your events moving at Goâspeed.
Installation
go get github.com/Raezil/GoEventBus
Quick Start
package main
import (
"context"
"fmt"
"github.com/Raezil/GoEventBus"
)
func main() {
// Create a dispatcher mapping projections to contextâaware handlers
dispatcher := GoEventBus.Dispatcher{
"user_created": func(ctx context.Context, args map[string]any) (GoEventBus.Result, error) {
userID := args["id"].(string)
fmt.Println("User created with ID:", userID)
return GoEventBus.Result{Message: "handled user_created"}, nil
},
}
// Initialise an EventStore with a 64K ring buffer and DropOldest overrun policy
store := GoEventBus.NewEventStore(&dispatcher, 1<<16, GoEventBus.DropOldest)
// Optionally run handlers asynchronously
store.Async = true
// Enqueue an event (always pass a context)
_ = store.Subscribe(context.Background(), GoEventBus.Event{
ID: "evt1",
Projection: "user_created",
Args: map[string]any{"id": "12345"},
})
// Process pending events
store.Publish()
// Retrieve metrics
published, processed, errors := store.Metrics()
fmt.Printf("published=%d processed=%d errors=%d", published, processed, errors)
}
API Reference
type Result
type Result struct {
Message string // Outcome message from handler
}
type Dispatcher
type Dispatcher map[interface{}]func(context.Context, map[string]any) (Result, error)
A map from projection keys to handler functions. Handlers receive a context.Context and an argument map, and return a Result and an error.
type Event
type Event struct {
ID string // Unique identifier for the event
Projection interface{} // Key to look up the handler in the dispatcher
Args map[string]any // Payload data for the event
}
type OverrunPolicy
type OverrunPolicy int
const (
DropOldest OverrunPolicy = iota // Default: discard oldest events
Block // Block until space is available
ReturnError // Fail fast with ErrBufferFull
)
type EventStore
type EventStore struct {
dispatcher *Dispatcher // Pointer to the dispatcher map
size uint64 // Buffer size (power of two)
buf []unsafe.Pointer // Ring buffer of event pointers
events []Event // Backing slice for Event data
head uint64 // Atomic write index
tail uint64 // Atomic read index
// Config flags
Async bool
OverrunPolicy OverrunPolicy
// Counters
publishedCount uint64
processedCount uint64
errorCount uint64
}
NewEventStore
func NewEventStore(dispatcher *Dispatcher, bufferSize uint64, policy OverrunPolicy) *EventStore
Creates a new EventStore with the provided dispatcher, ring buffer size, and overrun policy.
Subscribe
func (es *EventStore) Subscribe(ctx context.Context, e Event) error
Atomically enqueues an Event for later publication, applying backâpressure according to OverrunPolicy. If OverrunPolicy is ReturnError and the buffer is full, the function returns ErrBufferFull.
Publish
func (es *EventStore) Publish()
Dispatches all events from the last published position to the current head. If Async is true, handlers run in separate goroutines; otherwise they run in the caller's goroutine.
Metrics
func (es *EventStore) Metrics() (published, processed, errors uint64)
Returns the total count of published, processed, and errored events.
Backâpressure & Overrun Policies
GoEventBus provides three strategies for handling a saturated ring buffer:
| Policy |
Behaviour |
When to use |
DropOldest |
Atomically advances the read index, discarding the oldest event to make room for the new one. |
Lowâlatency scenarios where the newest data is most valuable and occasional loss is acceptable. |
Block |
Causes Subscribe to block (respecting its context) until space becomes available. |
Workloads that must not lose events but can tolerate the latency of backâpressure. |
ReturnError |
Subscribe returns ErrBufferFull immediately, allowing the caller to decide what to do. |
Pipelines where upstream logic controls retries and failures explicitly. |
DropOldest is the default behaviour and matches releases prior to April 2025.
đĄ Use Cases
GoEventBus is ideal for scenarios where lowâlatency, highâthroughput, and nonâblocking event dispatching is needed:
- đ Realâtime event pipelines (e.g. analytics, telemetry)
- đ„ Background task execution or job queues
- đ§© Microservice communication using inâprocess events
- âïž Observability/event sourcing in domainâdriven designs
- đ Inâmemory pub/sub for smallâscale distributed systems
- đź Game loops or simulations requiring lockâfree dispatching
Benchmarks
All benchmarks were run with Goâs testing harness (go test -bench .) on an -8 procs configuration. Numbers below are from the April 2025 release.
| Benchmark |
Iterations |
ns/op |
BenchmarkSubscribe-8 |
27,080,376 |
40.37 |
BenchmarkSubscribeParallel-8 |
26,418,999 |
38.42 |
BenchmarkPublish-8 |
295,661,464 |
3.910 |
BenchmarkPublishAfterPrefill-8 |
252,943,526 |
4.585 |
BenchmarkSubscribeLargePayload-8 |
1,613,017 |
771.5 |
BenchmarkPublishLargePayload-8 |
296,434,225 |
3.910 |
BenchmarkEventStore_Async-8 |
2,816,988 |
436.5 |
BenchmarkEventStore_Sync-8 |
2,638,519 |
428.5 |
BenchmarkFastHTTPSync-8 |
6,275,112 |
163.8 |
BenchmarkFastHTTPAsync-8 |
1,954,884 |
662.0 |
BenchmarkFastHTTPParallel-8 |
4,489,274 |
262.3 |
Contributing
Contributions, issues, and feature requests are welcome! Feel free to check the issues page.
License
Distributed under the MIT License. See LICENSE for more information.
GoEventBus
A blazingâfast, inâmemory, lockâfree event bus for Goâideal for lowâlatency pipelines, microservices, and game loops.

đ Table of Contents
Features
- Lockâfree ring buffer: Efficient event dispatching using atomic operations and cacheâline padding.
- Contextâaware handlers: Every handler now receives a
context.Context, enabling deadlines, cancellation and tracing.
- Backâpressure policies: Choose whether to drop, block, or error when the buffer is full.
- Configurable buffer size: Specify the ring buffer size (must be a power of two) when creating the store.
- Async or Sync processing: Toggle between synchronous handling or async goroutineâbased processing via the
Async flag.
- Metrics: Track published, processed, and errored event counts with a simple API.
- Simple API: Easy to subscribe, publish, and retrieve metrics.
Why GoEventBus?
Modern Go apps demand lightningâfast, nonâblocking communicationâbut channels can bottleneck and external brokers add latency, complexity and ops overhead. GoEventBus is your inâprocess, lockâfree solution:
- Microâlatency dispatch
Atomic, cacheâaligned ring buffers deliver subâmicrosecond handâoffsâno locks, no syscalls, zero garbage.
- Sync or Async at will
Flip a switch to run handlers inline for predictability or in goroutines for massive parallelism.
- Backâpressure your way
Choose from DropOldest, Block or ReturnError to match your systemâs tolerance for loss or latency.
- Builtâin observability
Expose counters for published, processed and errored events out of the boxâno extra instrumentation.
- Dropâin, zero deps
One import, no external services, no workers to manageâjust Go 1.21+ and youâre off.
Whether youâre building realâtime analytics, highâthroughput microservices, or game engines, GoEventBus keeps your events moving at Goâspeed.
Installation
go get github.com/Raezil/GoEventBus
Quick Start
package main
import (
"context"
"fmt"
"github.com/Raezil/GoEventBus"
)
func main() {
// Create a dispatcher mapping projections to contextâaware handlers
dispatcher := GoEventBus.Dispatcher{
"user_created": func(ctx context.Context, args map[string]any) (GoEventBus.Result, error) {
userID := args["id"].(string)
fmt.Println("User created with ID:", userID)
return GoEventBus.Result{Message: "handled user_created"}, nil
},
}
// Initialise an EventStore with a 64K ring buffer and DropOldest overrun policy
store := GoEventBus.NewEventStore(&dispatcher, 1<<16, GoEventBus.DropOldest)
// Optionally run handlers asynchronously
store.Async = true
// Enqueue an event (always pass a context)
_ = store.Subscribe(context.Background(), GoEventBus.Event{
ID: "evt1",
Projection: "user_created",
Args: map[string]any{"id": "12345"},
})
// Process pending events
store.Publish()
// Retrieve metrics
published, processed, errors := store.Metrics()
fmt.Printf("published=%d processed=%d errors=%d
", published, processed, errors)
}
API Reference
type Result
type Result struct {
Message string // Outcome message from handler
}
type Dispatcher
type Dispatcher map[interface{}]func(context.Context, map[string]any) (Result, error)
A map from projection keys to handler functions. Handlers receive a context.Context and an argument map, and return a Result and an error.
type Event
type Event struct {
ID string // Unique identifier for the event
Projection interface{} // Key to look up the handler in the dispatcher
Args map[string]any // Payload data for the event
}
type OverrunPolicy
type OverrunPolicy int
const (
DropOldest OverrunPolicy = iota // Default: discard oldest events
Block // Block until space is available
ReturnError // Fail fast with ErrBufferFull
)
type EventStore
type EventStore struct {
dispatcher *Dispatcher // Pointer to the dispatcher map
size uint64 // Buffer size (power of two)
buf []unsafe.Pointer // Ring buffer of event pointers
events []Event // Backing slice for Event data
head uint64 // Atomic write index
tail uint64 // Atomic read index
// Config flags
Async bool
OverrunPolicy OverrunPolicy
// Counters
publishedCount uint64
processedCount uint64
errorCount uint64
}
NewEventStore
func NewEventStore(dispatcher *Dispatcher, bufferSize uint64, policy OverrunPolicy) *EventStore
Creates a new EventStore with the provided dispatcher, ring buffer size, and overrun policy.
Subscribe
func (es *EventStore) Subscribe(ctx context.Context, e Event) error
Atomically enqueues an Event for later publication, applying backâpressure according to OverrunPolicy. If OverrunPolicy is ReturnError and the buffer is full, the function returns ErrBufferFull.
Publish
func (es *EventStore) Publish()
Dispatches all events from the last published position to the current head. If Async is true, handlers run in separate goroutines; otherwise they run in the caller's goroutine.
Metrics
func (es *EventStore) Metrics() (published, processed, errors uint64)
Returns the total count of published, processed, and errored events.
Backâpressure & Overrun Policies
GoEventBus provides three strategies for handling a saturated ring buffer:
| Policy |
Behaviour |
When to use |
DropOldest |
Atomically advances the read index, discarding the oldest event to make room for the new one. |
Lowâlatency scenarios where the newest data is most valuable and occasional loss is acceptable. |
Block |
Causes Subscribe to block (respecting its context) until space becomes available. |
Workloads that must not lose events but can tolerate the latency of backâpressure. |
ReturnError |
Subscribe returns ErrBufferFull immediately, allowing the caller to decide what to do. |
Pipelines where upstream logic controls retries and failures explicitly. |
DropOldest is the default behaviour and matches releases prior to April 2025.
đĄ Use Cases
GoEventBus is ideal for scenarios where lowâlatency, highâthroughput, and nonâblocking event dispatching is needed:
- đ Realâtime event pipelines (e.g. analytics, telemetry)
- đ„ Background task execution or job queues
- đ§© Microservice communication using inâprocess events
- âïž Observability/event sourcing in domainâdriven designs
- đ Inâmemory pub/sub for smallâscale distributed systems
- đź Game loops or simulations requiring lockâfree dispatching
Benchmarks
All benchmarks were run with Goâs testing harness (go test -bench .) on an -8 procs configuration. Numbers below are from the April 2025 release.
| Benchmark |
Iterations |
ns/op |
BenchmarkSubscribe-8 |
27,080,376 |
40.37 |
BenchmarkSubscribeParallel-8 |
26,418,999 |
38.42 |
BenchmarkPublish-8 |
295,661,464 |
3.910 |
BenchmarkPublishAfterPrefill-8 |
252,943,526 |
4.585 |
BenchmarkSubscribeLargePayload-8 |
1,613,017 |
771.5 |
BenchmarkPublishLargePayload-8 |
296,434,225 |
3.910 |
BenchmarkEventStore_Async-8 |
2,816,988 |
436.5 |
BenchmarkEventStore_Sync-8 |
2,638,519 |
428.5 |
BenchmarkFastHTTPSync-8 |
6,275,112 |
163.8 |
BenchmarkFastHTTPAsync-8 |
1,954,884 |
662.0 |
BenchmarkFastHTTPParallel-8 |
4,489,274 |
262.3 |
Contributing
Contributions, issues, and feature requests are welcome! Feel free to check the issues page.
License
Distributed under the MIT License. See LICENSE for more information.