149 lines
4.6 KiB
Go
149 lines
4.6 KiB
Go
// Package notify provides a thread-safe pub/sub notification system.
|
|
//
|
|
// The notify package implements a lightweight, in-memory notification system
|
|
// where subscribers can register to receive notifications. Each subscriber
|
|
// receives notifications through a buffered channel, allowing them to process
|
|
// messages at their own pace.
|
|
//
|
|
// # Features
|
|
//
|
|
// - Thread-safe concurrent operations
|
|
// - Configurable notification buffer size per Notifier
|
|
// - Unique subscriber IDs using cryptographic random generation
|
|
// - Targeted notifications to specific subscribers
|
|
// - Broadcast notifications to all subscribers
|
|
// - Idempotent unsubscribe operations
|
|
// - Graceful shutdown with Close()
|
|
// - Zero external dependencies (uses only Go standard library)
|
|
//
|
|
// # Basic Usage
|
|
//
|
|
// Create a notifier and subscribe:
|
|
//
|
|
// // Create notifier with 50-notification buffer per subscriber
|
|
// n := notify.NewNotifier(50)
|
|
// defer n.Close()
|
|
//
|
|
// // Subscribe to receive notifications
|
|
// sub, err := n.Subscribe()
|
|
// if err != nil {
|
|
// log.Fatal(err)
|
|
// }
|
|
// defer sub.Unsubscribe()
|
|
//
|
|
// // Listen for notifications
|
|
// go func() {
|
|
// for notification := range sub.Listen() {
|
|
// fmt.Printf("Received: %s - %s\n", notification.Level, notification.Message)
|
|
// }
|
|
// }()
|
|
//
|
|
// // Send a targeted notification
|
|
// n.Notify(notify.Notification{
|
|
// Target: sub.ID,
|
|
// Level: notify.LevelInfo,
|
|
// Message: "Hello subscriber!",
|
|
// })
|
|
//
|
|
// # Broadcasting
|
|
//
|
|
// Send notifications to all subscribers:
|
|
//
|
|
// // Broadcast to all subscribers
|
|
// n.NotifyAll(notify.Notification{
|
|
// Level: notify.LevelSuccess,
|
|
// Title: "System Update",
|
|
// Message: "All systems operational",
|
|
// })
|
|
//
|
|
// # Notification Levels
|
|
//
|
|
// The package provides predefined notification levels:
|
|
//
|
|
// - LevelSuccess: Success messages
|
|
// - LevelInfo: Informational messages
|
|
// - LevelWarn: Warning messages
|
|
// - LevelError: Error messages
|
|
//
|
|
// # Buffer Sizing
|
|
//
|
|
// The buffer size controls how many notifications can be queued per subscriber:
|
|
//
|
|
// - Small (10-25): Low latency, minimal memory, may drop messages if slow readers
|
|
// - Medium (50-100): Balanced approach (recommended for most applications)
|
|
// - Large (200-500): High throughput, handles bursts well
|
|
// - Unbuffered (0): No queuing, sends block until received
|
|
//
|
|
// # Thread Safety
|
|
//
|
|
// All operations are thread-safe and can be called from multiple goroutines:
|
|
//
|
|
// - Subscribe() - Safe to call concurrently
|
|
// - Unsubscribe() - Safe to call concurrently and multiple times
|
|
// - Notify() - Safe to call concurrently
|
|
// - NotifyAll() - Safe to call concurrently
|
|
// - Close() - Safe to call concurrently and multiple times
|
|
//
|
|
// # Graceful Shutdown
|
|
//
|
|
// Close the notifier to unsubscribe all subscribers and prevent new subscriptions:
|
|
//
|
|
// n := notify.NewNotifier(50)
|
|
// defer n.Close() // Ensures cleanup
|
|
//
|
|
// // Use notifier...
|
|
// // On defer or explicit Close():
|
|
// // - All subscribers are removed
|
|
// // - All notification channels are closed
|
|
// // - Future Subscribe() calls return error
|
|
// // - Notify() and NotifyAll() are no-ops
|
|
//
|
|
// # Notification Delivery
|
|
//
|
|
// Notifications are delivered using a TryLock pattern:
|
|
//
|
|
// - If the subscriber is available, the notification is queued
|
|
// - If the subscriber is busy unsubscribing, the notification is dropped
|
|
// - This prevents blocking on subscribers that are shutting down
|
|
//
|
|
// Buffered channels allow multiple notifications to queue, so subscribers
|
|
// don't need to read immediately. Once the buffer is full, subsequent
|
|
// notifications may be dropped if the subscriber is slow to read.
|
|
//
|
|
// # Example: Multi-Subscriber System
|
|
//
|
|
// func main() {
|
|
// n := notify.NewNotifier(100)
|
|
// defer n.Close()
|
|
//
|
|
// // Create multiple subscribers
|
|
// for i := 0; i < 5; i++ {
|
|
// sub, _ := n.Subscribe()
|
|
// go func(id int, s *notify.Subscriber) {
|
|
// for notif := range s.Listen() {
|
|
// log.Printf("Subscriber %d: %s", id, notif.Message)
|
|
// }
|
|
// }(i, sub)
|
|
// }
|
|
//
|
|
// // Broadcast to all
|
|
// n.NotifyAll(notify.Notification{
|
|
// Level: notify.LevelInfo,
|
|
// Message: "Server starting...",
|
|
// })
|
|
//
|
|
// time.Sleep(time.Second)
|
|
// }
|
|
//
|
|
// # Error Handling
|
|
//
|
|
// Subscribe() returns an error in these cases:
|
|
//
|
|
// - Notifier is closed (error: "notifier is closed")
|
|
// - Failed to generate unique ID after 10 attempts (extremely rare)
|
|
//
|
|
// Other operations (Notify, NotifyAll, Unsubscribe) do not return errors
|
|
// and handle edge cases gracefully (e.g., notifying non-existent subscribers
|
|
// is silently ignored).
|
|
package notify
|