Skip to main content
Version: 1.7

Presence

Periodic presence gossip for cluster-wide client and room counts in multi-node deployments.

import "github.com/KARTIKrocks/wshub"
  • Periodic heartbeat publishing of local client/room counts
  • Cluster-wide totals via GlobalClientCount and GlobalRoomCount
  • Automatic eviction of stale nodes (3 missed heartbeats)
  • O(1) change detection avoids re-gathering unchanged stats
  • Requires an adapter — no-op in single-node mode

Exact signatures live on pkg.go.dev.

Enabling Presence

Enable presence with WithPresence alongside an adapter. Each hub publishes its local stats at the given interval:

hub := wshub.NewHub(
wshub.WithAdapter(adapter), // required for presence
wshub.WithPresence(5*time.Second), // heartbeat interval (default: 5s)
wshub.WithNodeID("node-1"), // stable ID for debugging
)
go hub.Run()

When the interval is zero, the default of 5 seconds is used. Nodes that miss 3 consecutive heartbeats are automatically evicted from the totals.

Global Counts

MethodDescription
GlobalClientCount()Total connected clients across all nodes
GlobalRoomCount(room)Total clients in a room across all nodes
// Get cluster-wide totals
totalClients := hub.GlobalClientCount()
totalInRoom := hub.GlobalRoomCount("chat-general")

// In single-node mode (no adapter/presence), these return local counts
localClients := hub.ClientCount() // always local
globalClients := hub.GlobalClientCount() // local + remote when presence enabled

Full Example

A multi-node setup with the Redis adapter and presence:

package main

import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"

"github.com/KARTIKrocks/wshub"
wshubredis "github.com/KARTIKrocks/wshub/adapter/redis"
goredis "github.com/redis/go-redis/v9"
)

func main() {
rdb := goredis.NewClient(&goredis.Options{Addr: "localhost:6379"})
adapter := wshubredis.New(rdb)

nodeID, _ := os.Hostname()
hub := wshub.NewHub(
wshub.WithAdapter(adapter),
wshub.WithPresence(5*time.Second),
wshub.WithNodeID(nodeID),
wshub.WithMessageHandler(func(c *wshub.Client, msg *wshub.Message) error {
hub.Broadcast(msg.Data)
return nil
}),
)
go hub.Run()

// Expose cluster stats
http.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "local: %d, global: %d\n",
hub.ClientCount(), hub.GlobalClientCount())
})
http.HandleFunc("/ws", hub.HandleHTTP())

srv := &http.Server{Addr: ":8080"}
go srv.ListenAndServe()

quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
hub.Shutdown(ctx)
srv.Shutdown(ctx)
}