Files
go-redis/pubsub_test.go
Vedant Madane 9bfbc24c9e fix(pubsub): avoid panics on Channel conflict and Ring empty subscribe (#3968)
* fix(pubsub): avoid panics on Channel conflict and Ring empty subscribe

* fix(pubsub): avoid data race on Channel conflict warn

Conflict-path logging used getContext(), which reads c.cmd while
the other Channel Receive loop may write it — fails CI under -race.

Closes nothing; Refs #3761

* fix(pubsub): prioritize closed state over stickyErr on conn retrieval

* fix(pubsub): override stickyErr on Close for channel exit

Match SingleConnPool.Close by setting stickyErr to ErrClosed so
failed Ring PubSub Channel receivers always observe the closed
exit signal. Add .gitattributes to keep Go sources on LF.

* fix(pubsub): address review feedback on sticky error channel handling and docs

- Remove dead stickyErr write in Close()
- Short-circuit Channel() and ChannelWithSubscriptions() when stickyErr is set to return an already-closed channel and log a warning
- Add warning log in failedPubSub for visibility
- Update doc comments for Subscribe/PSubscribe/SSubscribe and Channel methods
- Remove repo-wide .gitattributes

Signed-off-by: Vedant Madane <6527493+VedantMadane@users.noreply.github.com>

---------

Signed-off-by: Vedant Madane <6527493+VedantMadane@users.noreply.github.com>
2026-10-19 15:07:24 +03:00

724 lines
20 KiB
Go

package redis_test
import (
"io"
"net"
"sync"
"time"
. "github.com/bsm/ginkgo/v2"
. "github.com/bsm/gomega"
"github.com/redis/go-redis/v9"
)
var _ = Describe("PubSub", func() {
var client *redis.Client
BeforeEach(func() {
opt := redisOptions()
opt.MinIdleConns = 0
opt.ConnMaxLifetime = 0
client = redis.NewClient(opt)
Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
})
AfterEach(func() {
Expect(client.Close()).NotTo(HaveOccurred())
})
It("implements Stringer", func() {
pubsub := client.PSubscribe(ctx, "mychannel*")
defer pubsub.Close()
Expect(pubsub.String()).To(Equal("PubSub(mychannel*)"))
})
It("should support pattern matching", func() {
pubsub := client.PSubscribe(ctx, "mychannel*")
defer pubsub.Close()
{
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
subscr := msgi.(*redis.Subscription)
Expect(subscr.Kind).To(Equal("psubscribe"))
Expect(subscr.Channel).To(Equal("mychannel*"))
Expect(subscr.Count).To(Equal(1))
}
{
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err.(net.Error).Timeout()).To(Equal(true))
Expect(msgi).To(BeNil())
}
n, err := client.Publish(ctx, "mychannel1", "hello").Result()
Expect(err).NotTo(HaveOccurred())
Expect(n).To(Equal(int64(1)))
Expect(pubsub.PUnsubscribe(ctx, "mychannel*")).NotTo(HaveOccurred())
{
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
subscr := msgi.(*redis.Message)
Expect(subscr.Channel).To(Equal("mychannel1"))
Expect(subscr.Pattern).To(Equal("mychannel*"))
Expect(subscr.Payload).To(Equal("hello"))
}
{
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
subscr := msgi.(*redis.Subscription)
Expect(subscr.Kind).To(Equal("punsubscribe"))
Expect(subscr.Channel).To(Equal("mychannel*"))
Expect(subscr.Count).To(Equal(0))
}
stats := client.PoolStats()
Expect(stats.Misses).To(Equal(uint32(1)))
})
It("should receive hash field subkey notifications", Label("hash-expiration", "NonRedisEnterprise"), func() {
SkipBeforeRedisVersion("8.8", "subkeyspace notifications require Redis 8.8")
config, err := client.ConfigGet(ctx, "notify-keyspace-events").Result()
Expect(err).NotTo(HaveOccurred())
previousNotifyConfig := config["notify-keyspace-events"]
Expect(client.ConfigSet(ctx, "notify-keyspace-events", "STh").Err()).NotTo(HaveOccurred())
defer client.ConfigSet(ctx, "notify-keyspace-events", previousNotifyConfig)
const (
hashKey = "skn:hash"
field = "field-alpha"
hexpireChannel = "__subkeyevent@0__:hexpire"
expiredChannel = "__subkeyevent@0__:hexpired"
expectedPayload = "8:skn:hash|11:field-alpha"
)
Expect(client.Del(ctx, hashKey).Err()).NotTo(HaveOccurred())
pubsub := client.Subscribe(ctx, hexpireChannel, expiredChannel)
defer pubsub.Close()
for _, expected := range []redis.Subscription{
{Kind: "subscribe", Channel: hexpireChannel, Count: 1},
{Kind: "subscribe", Channel: expiredChannel, Count: 2},
} {
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
Expect(msgi).To(Equal(&expected))
}
Expect(client.HSet(ctx, hashKey, field, "value").Err()).NotTo(HaveOccurred())
res, err := client.HPExpire(ctx, hashKey, 50*time.Millisecond, field).Result()
Expect(err).NotTo(HaveOccurred())
Expect(res).To(Equal([]int64{1}))
seen := make(map[string]string)
deadline := time.Now().Add(10 * time.Second)
for time.Now().Before(deadline) &&
(seen[hexpireChannel] == "" || seen[expiredChannel] == "") {
msgi, err := pubsub.ReceiveTimeout(ctx, 500*time.Millisecond)
if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue
}
Expect(err).NotTo(HaveOccurred())
}
msg, ok := msgi.(*redis.Message)
if !ok {
continue
}
if msg.Channel != hexpireChannel && msg.Channel != expiredChannel {
continue
}
Expect(msg.Pattern).To(BeEmpty())
Expect(msg.Payload).To(Equal(expectedPayload))
Expect(msg.PayloadSlice).To(BeEmpty())
seen[msg.Channel] = msg.Payload
}
Expect(seen).To(HaveKeyWithValue(hexpireChannel, expectedPayload))
Expect(seen).To(HaveKeyWithValue(expiredChannel, expectedPayload))
})
It("should pub/sub channels", func() {
channels, err := client.PubSubChannels(ctx, "mychannel*").Result()
Expect(err).NotTo(HaveOccurred())
Expect(channels).To(BeEmpty())
pubsub := client.Subscribe(ctx, "mychannel", "mychannel2")
defer pubsub.Close()
// sleep a bit to make sure redis knows about the subscriptions
time.Sleep(10 * time.Millisecond)
channels, err = client.PubSubChannels(ctx, "mychannel*").Result()
Expect(err).NotTo(HaveOccurred())
Expect(channels).To(ConsistOf([]string{"mychannel", "mychannel2"}))
channels, err = client.PubSubChannels(ctx, "").Result()
Expect(err).NotTo(HaveOccurred())
Expect(channels).To(BeEmpty())
channels, err = client.PubSubChannels(ctx, "*").Result()
Expect(err).NotTo(HaveOccurred())
Expect(len(channels)).To(BeNumerically(">=", 2))
})
It("should sharded pub/sub channels", func() {
channels, err := client.PubSubShardChannels(ctx, "mychannel*").Result()
Expect(err).NotTo(HaveOccurred())
Expect(channels).To(BeEmpty())
pubsub := client.SSubscribe(ctx, "mychannel", "mychannel2")
defer pubsub.Close()
// Let Redis process the ssubscribe command.
time.Sleep(10 * time.Millisecond)
channels, err = client.PubSubShardChannels(ctx, "mychannel*").Result()
Expect(err).NotTo(HaveOccurred())
Expect(channels).To(ConsistOf([]string{"mychannel", "mychannel2"}))
channels, err = client.PubSubShardChannels(ctx, "").Result()
Expect(err).NotTo(HaveOccurred())
Expect(channels).To(BeEmpty())
channels, err = client.PubSubShardChannels(ctx, "*").Result()
Expect(err).NotTo(HaveOccurred())
Expect(len(channels)).To(BeNumerically(">=", 2))
nums, err := client.PubSubShardNumSub(ctx, "mychannel", "mychannel2", "mychannel3").Result()
Expect(err).NotTo(HaveOccurred())
Expect(nums).To(Equal(map[string]int64{
"mychannel": 1,
"mychannel2": 1,
"mychannel3": 0,
}))
})
It("should return the numbers of subscribers", func() {
pubsub := client.Subscribe(ctx, "mychannel", "mychannel2")
defer pubsub.Close()
// sleep a bit to make sure redis knows about the subscriptions
time.Sleep(10 * time.Millisecond)
channels, err := client.PubSubNumSub(ctx, "mychannel", "mychannel2", "mychannel3").Result()
Expect(err).NotTo(HaveOccurred())
Expect(channels).To(Equal(map[string]int64{
"mychannel": 1,
"mychannel2": 1,
"mychannel3": 0,
}))
})
It("should return the numbers of subscribers by pattern", func() {
num, err := client.PubSubNumPat(ctx).Result()
Expect(err).NotTo(HaveOccurred())
Expect(num).To(Equal(int64(0)))
pubsub := client.PSubscribe(ctx, "*")
defer pubsub.Close()
// sleep a bit to make sure redis knows about the subscriptions
time.Sleep(10 * time.Millisecond)
num, err = client.PubSubNumPat(ctx).Result()
Expect(err).NotTo(HaveOccurred())
Expect(num).To(Equal(int64(1)))
})
It("should pub/sub", func() {
pubsub := client.Subscribe(ctx, "mychannel", "mychannel2")
defer pubsub.Close()
{
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
subscr := msgi.(*redis.Subscription)
Expect(subscr.Kind).To(Equal("subscribe"))
Expect(subscr.Channel).To(Equal("mychannel"))
Expect(subscr.Count).To(Equal(1))
}
{
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
subscr := msgi.(*redis.Subscription)
Expect(subscr.Kind).To(Equal("subscribe"))
Expect(subscr.Channel).To(Equal("mychannel2"))
Expect(subscr.Count).To(Equal(2))
}
{
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err.(net.Error).Timeout()).To(Equal(true))
Expect(msgi).NotTo(HaveOccurred())
}
n, err := client.Publish(ctx, "mychannel", "hello").Result()
Expect(err).NotTo(HaveOccurred())
Expect(n).To(Equal(int64(1)))
n, err = client.Publish(ctx, "mychannel2", "hello2").Result()
Expect(err).NotTo(HaveOccurred())
Expect(n).To(Equal(int64(1)))
Expect(pubsub.Unsubscribe(ctx, "mychannel", "mychannel2")).NotTo(HaveOccurred())
{
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
msg := msgi.(*redis.Message)
Expect(msg.Channel).To(Equal("mychannel"))
Expect(msg.Payload).To(Equal("hello"))
}
{
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
msg := msgi.(*redis.Message)
Expect(msg.Channel).To(Equal("mychannel2"))
Expect(msg.Payload).To(Equal("hello2"))
}
{
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
subscr := msgi.(*redis.Subscription)
Expect(subscr.Kind).To(Equal("unsubscribe"))
Expect(subscr.Channel).To(Equal("mychannel"))
Expect(subscr.Count).To(Equal(1))
}
{
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
subscr := msgi.(*redis.Subscription)
Expect(subscr.Kind).To(Equal("unsubscribe"))
Expect(subscr.Channel).To(Equal("mychannel2"))
Expect(subscr.Count).To(Equal(0))
}
stats := client.PoolStats()
Expect(stats.Misses).To(Equal(uint32(1)))
})
It("should sharded pub/sub", func() {
pubsub := client.SSubscribe(ctx, "mychannel", "mychannel2")
defer pubsub.Close()
{
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
subscr := msgi.(*redis.Subscription)
Expect(subscr.Kind).To(Equal("ssubscribe"))
Expect(subscr.Channel).To(Equal("mychannel"))
Expect(subscr.Count).To(Equal(1))
}
{
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
subscr := msgi.(*redis.Subscription)
Expect(subscr.Kind).To(Equal("ssubscribe"))
Expect(subscr.Channel).To(Equal("mychannel2"))
Expect(subscr.Count).To(Equal(2))
}
{
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err.(net.Error).Timeout()).To(Equal(true))
Expect(msgi).NotTo(HaveOccurred())
}
n, err := client.SPublish(ctx, "mychannel", "hello").Result()
Expect(err).NotTo(HaveOccurred())
Expect(n).To(Equal(int64(1)))
n, err = client.SPublish(ctx, "mychannel2", "hello2").Result()
Expect(err).NotTo(HaveOccurred())
Expect(n).To(Equal(int64(1)))
Expect(pubsub.SUnsubscribe(ctx, "mychannel", "mychannel2")).NotTo(HaveOccurred())
{
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
msg := msgi.(*redis.Message)
Expect(msg.Channel).To(Equal("mychannel"))
Expect(msg.Payload).To(Equal("hello"))
}
{
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
msg := msgi.(*redis.Message)
Expect(msg.Channel).To(Equal("mychannel2"))
Expect(msg.Payload).To(Equal("hello2"))
}
{
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
subscr := msgi.(*redis.Subscription)
Expect(subscr.Kind).To(Equal("sunsubscribe"))
Expect(subscr.Channel).To(Equal("mychannel"))
Expect(subscr.Count).To(Equal(1))
}
{
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
subscr := msgi.(*redis.Subscription)
Expect(subscr.Kind).To(Equal("sunsubscribe"))
Expect(subscr.Channel).To(Equal("mychannel2"))
Expect(subscr.Count).To(Equal(0))
}
stats := client.PoolStats()
Expect(stats.Misses).To(Equal(uint32(1)))
})
It("should ping/pong", func() {
pubsub := client.Subscribe(ctx, "mychannel")
defer pubsub.Close()
_, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
err = pubsub.Ping(ctx, "")
Expect(err).NotTo(HaveOccurred())
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
pong := msgi.(*redis.Pong)
Expect(pong.Payload).To(Equal(""))
})
It("should ping/pong with payload", func() {
pubsub := client.Subscribe(ctx, "mychannel")
defer pubsub.Close()
_, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
err = pubsub.Ping(ctx, "hello")
Expect(err).NotTo(HaveOccurred())
msgi, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
pong := msgi.(*redis.Pong)
Expect(pong.Payload).To(Equal("hello"))
})
It("should multi-ReceiveMessage", func() {
pubsub := client.Subscribe(ctx, "mychannel")
defer pubsub.Close()
subscr, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
Expect(subscr).To(Equal(&redis.Subscription{
Kind: "subscribe",
Channel: "mychannel",
Count: 1,
}))
err = client.Publish(ctx, "mychannel", "hello").Err()
Expect(err).NotTo(HaveOccurred())
err = client.Publish(ctx, "mychannel", "world").Err()
Expect(err).NotTo(HaveOccurred())
msg, err := pubsub.ReceiveMessage(ctx)
Expect(err).NotTo(HaveOccurred())
Expect(msg.Channel).To(Equal("mychannel"))
Expect(msg.Payload).To(Equal("hello"))
msg, err = pubsub.ReceiveMessage(ctx)
Expect(err).NotTo(HaveOccurred())
Expect(msg.Channel).To(Equal("mychannel"))
Expect(msg.Payload).To(Equal("world"))
})
It("returns an error when subscribe fails", func() {
pubsub := client.Subscribe(ctx)
defer pubsub.Close()
pubsub.SetNetConn(&badConn{
readErr: io.EOF,
writeErr: io.EOF,
})
err := pubsub.Subscribe(ctx, "mychannel")
Expect(err).To(MatchError("EOF"))
err = pubsub.Subscribe(ctx, "mychannel")
Expect(err).NotTo(HaveOccurred())
})
expectReceiveMessageOnError := func(pubsub *redis.PubSub) {
pubsub.SetNetConn(&badConn{
readErr: io.EOF,
writeErr: io.EOF,
})
step := make(chan struct{}, 3)
go func() {
defer GinkgoRecover()
Eventually(step).Should(Receive())
err := client.Publish(ctx, "mychannel", "hello").Err()
Expect(err).NotTo(HaveOccurred())
step <- struct{}{}
}()
_, err := pubsub.ReceiveMessage(ctx)
Expect(err).To(Equal(io.EOF))
step <- struct{}{}
msg, err := pubsub.ReceiveMessage(ctx)
Expect(err).NotTo(HaveOccurred())
Expect(msg.Channel).To(Equal("mychannel"))
Expect(msg.Payload).To(Equal("hello"))
Eventually(step).Should(Receive())
}
It("Subscribe should reconnect on ReceiveMessage error", func() {
pubsub := client.Subscribe(ctx, "mychannel")
defer pubsub.Close()
subscr, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
Expect(subscr).To(Equal(&redis.Subscription{
Kind: "subscribe",
Channel: "mychannel",
Count: 1,
}))
expectReceiveMessageOnError(pubsub)
})
It("PSubscribe should reconnect on ReceiveMessage error", func() {
pubsub := client.PSubscribe(ctx, "mychannel")
defer pubsub.Close()
subscr, err := pubsub.ReceiveTimeout(ctx, time.Second)
Expect(err).NotTo(HaveOccurred())
Expect(subscr).To(Equal(&redis.Subscription{
Kind: "psubscribe",
Channel: "mychannel",
Count: 1,
}))
expectReceiveMessageOnError(pubsub)
})
It("should return on Close", func() {
pubsub := client.Subscribe(ctx, "mychannel")
defer pubsub.Close()
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer GinkgoRecover()
wg.Done()
defer wg.Done()
_, err := pubsub.ReceiveMessage(ctx)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(SatisfyAny(
Equal("redis: client is closed"),
ContainSubstring("use of closed network connection"),
))
}()
wg.Wait()
wg.Add(1)
Expect(pubsub.Close()).NotTo(HaveOccurred())
wg.Wait()
})
It("should ReceiveMessage without a subscription", func() {
timeout := 100 * time.Millisecond
pubsub := client.Subscribe(ctx)
defer pubsub.Close()
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer GinkgoRecover()
defer wg.Done()
time.Sleep(timeout)
err := pubsub.Subscribe(ctx, "mychannel")
Expect(err).NotTo(HaveOccurred())
time.Sleep(timeout)
err = client.Publish(ctx, "mychannel", "hello").Err()
Expect(err).NotTo(HaveOccurred())
}()
msg, err := pubsub.ReceiveMessage(ctx)
Expect(err).NotTo(HaveOccurred())
Expect(msg.Channel).To(Equal("mychannel"))
Expect(msg.Payload).To(Equal("hello"))
wg.Wait()
})
It("handles big message payload", func() {
pubsub := client.Subscribe(ctx, "mychannel")
defer pubsub.Close()
ch := pubsub.Channel()
bigVal := bigVal()
err := client.Publish(ctx, "mychannel", bigVal).Err()
Expect(err).NotTo(HaveOccurred())
var msg *redis.Message
Eventually(ch).Should(Receive(&msg))
Expect(msg.Channel).To(Equal("mychannel"))
Expect(msg.Payload).To(Equal(string(bigVal)))
})
It("supports concurrent Ping and Receive", func() {
const N = 100
pubsub := client.Subscribe(ctx, "mychannel")
defer pubsub.Close()
done := make(chan struct{})
go func() {
defer GinkgoRecover()
for i := 0; i < N; i++ {
_, err := pubsub.ReceiveTimeout(ctx, 5*time.Second)
Expect(err).NotTo(HaveOccurred())
}
close(done)
}()
for i := 0; i < N; i++ {
err := pubsub.Ping(ctx)
Expect(err).NotTo(HaveOccurred())
}
select {
case <-done:
case <-time.After(30 * time.Second):
Fail("timeout")
}
})
It("should ClientSetName on PubSub connection", func() {
pubsub := client.Subscribe(ctx, "test-channel")
defer pubsub.Close()
// Wait for subscription to be established
_, err := pubsub.Receive(ctx)
Expect(err).NotTo(HaveOccurred())
err = pubsub.ClientSetName(ctx, "my-subscriber")
Expect(err).NotTo(HaveOccurred())
// PubSub.ClientSetName only writes the command; the +OK reply is
// drained here. This also synchronizes us with the server so that
// the subsequent CLIENT LIST on a different connection observes
// the rename (CLIENT LIST may otherwise race the SETNAME packet).
_, err = pubsub.Receive(ctx)
Expect(err).NotTo(HaveOccurred())
// Verify the name is set via CLIENT LIST
clientList, err := client.ClientList(ctx).Result()
Expect(err).NotTo(HaveOccurred())
Expect(clientList).To(ContainSubstring("name=my-subscriber"))
})
It("should ChannelMessage", func() {
pubsub := client.Subscribe(ctx, "mychannel")
defer pubsub.Close()
// Wait for the SUBSCRIBE confirmation before calling Channel(), so the
// server is guaranteed to have registered the subscription before we
// Publish. Once Channel() starts its pump goroutine, all subsequent
// reads happen there and Subscription frames are filtered out.
_, err := pubsub.ReceiveTimeout(ctx, 5*time.Second)
Expect(err).NotTo(HaveOccurred())
ch := pubsub.Channel(
redis.WithChannelSize(10),
redis.WithChannelHealthCheckInterval(time.Second),
)
text := "test channel message"
err = client.Publish(ctx, "mychannel", text).Err()
Expect(err).NotTo(HaveOccurred())
var msg *redis.Message
Eventually(ch, 5*time.Second).Should(Receive(&msg))
Expect(msg.Channel).To(Equal("mychannel"))
Expect(msg.Payload).To(Equal(text))
})
It("closes Channel when PubSub with sticky error is consumed", func() {
ring := redis.NewRing(&redis.RingOptions{})
defer ring.Close()
// Ring.Subscribe with no channels returns a failedPubSub with stickyErr
pubsub := ring.Subscribe(ctx)
ch := pubsub.Channel()
Eventually(ch, 5*time.Second).Should(BeClosed())
// Verify range does not hang
for range ch {
Fail("should not receive messages from failed pubsub channel")
}
Expect(pubsub.Close()).To(Succeed())
})
It("closes ChannelWithSubscriptions when PubSub with sticky error is consumed", func() {
ring := redis.NewRing(&redis.RingOptions{})
defer ring.Close()
pubsub := ring.Subscribe(ctx)
ch := pubsub.ChannelWithSubscriptions()
Eventually(ch, 5*time.Second).Should(BeClosed())
for range ch {
Fail("should not receive messages from failed pubsub channel")
}
Expect(pubsub.Close()).To(Succeed())
})
It("returns closed channel when calling conflicting Channel methods", func() {
pubsub := client.Subscribe(ctx, "mychannel")
defer pubsub.Close()
ch1 := pubsub.Channel()
Expect(ch1).NotTo(BeNil())
// Second call with ChannelWithSubscriptions should return closed channel
ch2 := pubsub.ChannelWithSubscriptions()
Eventually(ch2, 5*time.Second).Should(BeClosed())
})
})