diff --git a/cmd/crm/main.go b/cmd/crm/main.go index 0cb219580..bc62e0e30 100644 --- a/cmd/crm/main.go +++ b/cmd/crm/main.go @@ -4,8 +4,8 @@ import ( "log" "os" - "github.com/crusttech/crust/crm" "github.com/crusttech/crust/auth" + "github.com/crusttech/crust/crm" "github.com/crusttech/crust/rbac" ) diff --git a/sam/repository/pubsub.go b/sam/repository/pubsub.go new file mode 100644 index 000000000..0b8cbd89b --- /dev/null +++ b/sam/repository/pubsub.go @@ -0,0 +1,120 @@ +package repository + +import ( + "context" + "time" + + "github.com/gomodule/redigo/redis" + "github.com/pkg/errors" + + "github.com/crusttech/crust/config" +) + +type PubSub struct { + ctx context.Context + config *config.PubSub +} + +func (PubSub) New(ctx context.Context) (*PubSub, error) { + if err := flags.PubSub.Validate(); err != nil { + return nil, err + } + return &PubSub{ctx, flags.PubSub}, nil +} + +func (ps *PubSub) With(ctx context.Context) *PubSub { + return &PubSub{ctx, ps.config} +} + +func (ps *PubSub) dial() (redis.Conn, error) { + return redis.Dial( + "tcp", + flags.PubSub.RedisAddr, + redis.DialReadTimeout(ps.config.PingTimeout+ps.config.Timeout), + redis.DialWriteTimeout(ps.config.Timeout), + ) +} + +func (ps *PubSub) Subscribe(onStart func() error, onMessage func(channel string, payload []byte) error, channels ...string) error { + if len(channels) == 0 { + return errors.New("Need to subscribe at least to one channel") + } + + // main redis connection + conn, err := ps.dial() + if err != nil { + return err + } + defer conn.Close() + + // pubsub object + psc := redis.PubSubConn{Conn: conn} + if err := psc.Subscribe(redis.Args{}.AddFlat(channels)...); err != nil { + return err + } + + done := make(chan error, 1) + + // Start a goroutine to receive notifications from the server. + go func() { + for { + switch n := psc.Receive().(type) { + case error: + done <- n + return + case redis.Message: + if err := onMessage(n.Channel, n.Data); err != nil { + done <- err + return + } + case redis.Subscription: + switch n.Count { + case len(channels): + // Notify application when all channels are subscribed. + if err := onStart(); err != nil { + done <- err + return + } + case 0: + // Return from the goroutine when all channels are unsubscribed. + done <- nil + return + } + } + } + }() + + ticker := time.NewTicker(ps.config.PingPeriod) + defer ticker.Stop() + + cleanup := func(err error) error { + psc.Unsubscribe() + return err + } + + for { + select { + case <-ticker.C: + if err := psc.Ping(""); err != nil { + return cleanup(err) + } + case <-ps.ctx.Done(): + return cleanup(ps.ctx.Err()) + case err := <-done: + return err + } + } +} + +func (ps *PubSub) Publish(channel, payload string) error { + // main redis connection + conn, err := ps.dial() + if err != nil { + return err + } + defer conn.Close() + + // publish payload on channel + _, err = conn.Do("PUBLISH", channel, payload) + return err +} diff --git a/sam/service/pubsub.go b/sam/service/pubsub.go index 12b57c308..608d49a7f 100644 --- a/sam/service/pubsub.go +++ b/sam/service/pubsub.go @@ -2,126 +2,18 @@ package service import ( "context" - "time" - "github.com/gomodule/redigo/redis" - "github.com/pkg/errors" - - "github.com/crusttech/crust/config" + "github.com/crusttech/crust/sam/repository" ) type PubSub struct { - ctx context.Context - - redisServerAddr string - - healthCheckInterval time.Duration + *repository.PubSub } -func (ps PubSub) New(config *config.PubSub, ctx context.Context) (*PubSub, error) { - if err := config.Validate(); err != nil { +func (PubSub) New(ctx context.Context) (*PubSub, error) { + rpo, err := repository.PubSub{}.New(ctx) + if err != nil { return nil, err } - return &PubSub{ - ctx: ctx, - redisServerAddr: config.RedisAddr, - healthCheckInterval: time.Minute, - }, nil -} - -func (ps *PubSub) With(ctx context.Context) *PubSub { - return &PubSub{ - ctx: ctx, - healthCheckInterval: ps.healthCheckInterval, - } -} - -func (ps *PubSub) dial() (redis.Conn, error) { - readTimeout := redis.DialReadTimeout(ps.healthCheckInterval + 10*time.Second) - writeTimeout := redis.DialWriteTimeout(10 * time.Second) - return redis.Dial("tcp", ps.redisServerAddr, readTimeout, writeTimeout) -} - -func (ps *PubSub) Subscribe(onStart func() error, onMessage func(channel string, payload []byte) error, channels ...string) error { - if len(channels) == 0 { - return errors.New("Need to subscribe at least to one channel") - } - - // main redis connection - conn, err := ps.dial() - if err != nil { - return err - } - defer conn.Close() - - // pubsub object - psc := redis.PubSubConn{Conn: conn} - if err := psc.Subscribe(redis.Args{}.AddFlat(channels)...); err != nil { - return err - } - - done := make(chan error, 1) - - // Start a goroutine to receive notifications from the server. - go func() { - for { - switch n := psc.Receive().(type) { - case error: - done <- n - return - case redis.Message: - if err := onMessage(n.Channel, n.Data); err != nil { - done <- err - return - } - case redis.Subscription: - switch n.Count { - case len(channels): - // Notify application when all channels are subscribed. - if err := onStart(); err != nil { - done <- err - return - } - case 0: - // Return from the goroutine when all channels are unsubscribed. - done <- nil - return - } - } - } - }() - - ticker := time.NewTicker(ps.healthCheckInterval) - defer ticker.Stop() - - cleanup := func(err error) error { - psc.Unsubscribe() - return err - } - - for { - select { - case <-ticker.C: - if err := psc.Ping(""); err != nil { - return cleanup(err) - } - case <-ps.ctx.Done(): - return cleanup(ps.ctx.Err()) - case err := <-done: - return err - } - } -} - -func (ps *PubSub) Publish(channel, payload string) error { - // main redis connection - conn, err := ps.dial() - if err != nil { - return err - } - defer conn.Close() - - // publish payload on channel - _, err = conn.Do("PUBLISH", channel, payload) - return err + return &PubSub{rpo}, nil } diff --git a/sam/websocket/event_queue.go b/sam/websocket/event_queue.go index 01ff82fe3..8b955823f 100644 --- a/sam/websocket/event_queue.go +++ b/sam/websocket/event_queue.go @@ -67,7 +67,7 @@ func (eq *eventQueue) feedSessions(ctx context.Context, config *repository.Flags // feed events from redis into newMessageEvent channel if config.PubSub.Mode == "redis" { - pubsub, err := service.PubSub{}.New(config.PubSub, ctx) + pubsub, err := service.PubSub{}.New(ctx) if err != nil { log.Printf("PubSub: Error when starting in mode=redis, %+v\n", err) log.Println("PubSub: Reverting to polling mode")