func (e *RedisEngine) runPubSub() { conn := redis.PubSubConn{Conn: e.pool.Get()} defer conn.Close() e.app.RLock() adminChannel := e.app.config.AdminChannel controlChannel := e.app.config.ControlChannel e.app.RUnlock() done := make(chan struct{}) defer close(done) // Run subscriber routine go func() { logger.INFO.Println("Starting RedisEngine Subscriber") defer func() { logger.INFO.Println("Stopping RedisEngine Subscriber") }() for { select { case <-done: return case r := <-e.subCh: // Something to subscribe chIDs := []interface{}{r.Channel} batch := []subRequest{r} // Try to gather as many others as we can without waiting fillBatchFromChan(e.subCh, &batch, &chIDs, RedisSubscribeBatchLimit) // Send them all err := conn.Subscribe(chIDs...) if err != nil { // Subscribe error is fatal logger.ERROR.Printf("RedisEngine Subscriber error: %v\n", err) for i := range batch { batch[i].done(err) } // Close conn, this should cause Receive to return with err below // and whole runPubSub method to restart conn.Close() return } for i := range batch { batch[i].done(nil) } case r := <-e.unSubCh: // Something to subscribe chIDs := []interface{}{r.Channel} batch := []subRequest{r} // Try to gather as many others as we can without waiting fillBatchFromChan(e.unSubCh, &batch, &chIDs, RedisSubscribeBatchLimit) // Send them all err := conn.Unsubscribe(chIDs...) if err != nil { // Subscribe error is fatal logger.ERROR.Printf("RedisEngine Unsubscriber error: %v\n", err) for i := range batch { batch[i].done(err) } // Close conn, this should cause Receive to return with err below // and whole runPubSub method to restart conn.Close() return } for i := range batch { batch[i].done(nil) } } } }() // Subscribe to channels we need in bulk. // We don't care if they fail since conn will be closed and we'll retry // if they do anyway. // This saves a lot of allocating of pointless chans... r := newSubRequest(adminChannel, false) e.subCh <- r r = newSubRequest(controlChannel, false) e.subCh <- r for _, chID := range e.app.clients.channels() { r = newSubRequest(chID, false) e.subCh <- r } for { switch n := conn.Receive().(type) { case redis.Message: e.app.handleMsg(ChannelID(n.Channel), n.Data) case redis.Subscription: case error: logger.ERROR.Printf("RedisEngine Receiver error: %v\n", n) return } } }
// Applications can receive pushed messages from one goroutine and manage subscriptions from another goroutine. func ExamplePubSubConn() { c, err := dial() if err != nil { panic(err) } defer c.Close() var wg sync.WaitGroup wg.Add(2) psc := redis.PubSubConn{Conn: c} // This goroutine receives and prints pushed notifications from the server. // The goroutine exits when the connection is unsubscribed from all // channels or there is an error. go func() { defer wg.Done() for { switch n := psc.Receive().(type) { case redis.Message: fmt.Printf("Message: %s %s\n", n.Channel, n.Data) case redis.PMessage: fmt.Printf("PMessage: %s %s %s\n", n.Pattern, n.Channel, n.Data) case redis.Subscription: fmt.Printf("Subscription: %s %s %d\n", n.Kind, n.Channel, n.Count) if n.Count == 0 { return } case error: fmt.Printf("error: %v\n", n) return } } }() // This goroutine manages subscriptions for the connection. go func() { defer wg.Done() psc.Subscribe("example") psc.PSubscribe("p*") // The following function calls publish a message using another // connection to the Redis server. publish("example", "hello") publish("example", "world") publish("pexample", "foo") publish("pexample", "bar") // Unsubscribe from all connections. This will cause the receiving // goroutine to exit. psc.Unsubscribe() psc.PUnsubscribe() }() wg.Wait() // Output: // Subscription: subscribe example 1 // Subscription: psubscribe p* 2 // Message: example hello // Message: example world // PMessage: p* pexample foo // PMessage: p* pexample bar // Subscription: unsubscribe example 1 // Subscription: punsubscribe p* 0 }