├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── connector.go ├── connector_test.go └── types.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | #JetBrains IDE Golang 15 | .idea/ 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | go: 4 | - "1.11.x" 5 | - master 6 | - tip 7 | 8 | script: go test ./... -v -cover 9 | 10 | before_install: 11 | - go get github.com/mattn/goveralls 12 | script: 13 | - $GOPATH/bin/goveralls -service=travis-ci 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Babiv Sergey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/sbabiv/rmqconn.svg?branch=master)](https://travis-ci.org/sbabiv/rmqconn) 2 | [![Coverage Status](https://coveralls.io/repos/github/sbabiv/rmqconn/badge.svg?branch=master)](https://coveralls.io/github/sbabiv/rmqconn?branch=master) 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/sbabiv/rmqconn)](https://goreportcard.com/report/github.com/sbabiv/rmqconn) 4 | [![GoDoc](https://godoc.org/github.com/sbabiv/rmqconn?status.svg)](https://godoc.org/github.com/sbabiv/rmqconn) 5 | [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go#messaging) 6 | 7 | # rmqconn 8 | RabbitMQ Reconnection for Golang 9 | 10 | Wrapper over `amqp.Connection` and `amqp.Dial`. Allowing to do a reconnection when the connection is broken before forcing the call to the Close () method to be closed 11 | 12 | Use the default method `func Dial (url string) (Conner, error)` to connect to the server. 13 | You can implement your connection function and pass it to `rmqconn.Open("", customFunc)` 14 | 15 | ## Getting started 16 | 17 | #### 1. install 18 | 19 | ``` sh 20 | go get -u github.com/sbabiv/rmqconn 21 | ``` 22 | 23 | #### 2. use it 24 | 25 | ```Go 26 | conn, err := rmqconn.Open("amqp://usr:pwd@host:5672", rmqconn.Dial) 27 | defer conn.Close() 28 | 29 | if err != nil { 30 | return 31 | } 32 | 33 | if conn.IsConnected() { 34 | ch, err := conn.GetChannel() 35 | if err != nil { 36 | return 37 | } 38 | defer ch.Close() 39 | 40 | err = ch.Publish("", "queueName", false, false, amqp.Publishing{ 41 | Body: []byte("hello wolrd"), 42 | }) 43 | } 44 | ``` 45 | ## Licence 46 | [MIT](https://opensource.org/licenses/MIT) 47 | 48 | ## Author 49 | Babiv Sergey 50 | -------------------------------------------------------------------------------- /connector.go: -------------------------------------------------------------------------------- 1 | package rmqconn 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | "sync/atomic" 7 | "time" 8 | 9 | "github.com/streadway/amqp" 10 | ) 11 | 12 | 13 | //Conn connect instance 14 | type Conn struct { 15 | sync.Mutex 16 | connection Conner 17 | 18 | done chan struct{} 19 | notifyClose chan *amqp.Error 20 | 21 | isConnected int32 22 | attempts int8 23 | url string 24 | 25 | dial func(string) (Conner, error) 26 | } 27 | 28 | const ( 29 | disconnected int32 = 0 30 | connected int32 = 1 31 | closed int32 = -1 32 | 33 | maxAttempts int8 = 5 34 | ) 35 | 36 | var ( 37 | // ErrConnectionUnavailable remote server is unavailable or network is down 38 | ErrConnectionUnavailable = errors.New("connection unavailable") 39 | // ErrClsoe connection was closed earlier 40 | ErrClose = errors.New("connection was closed earlier") 41 | ) 42 | 43 | // Dial function wrapper amqp.Dial 44 | // For use amqp.DialConfig or amqp.DialTLS implement your func 45 | func Dial(url string) (Conner, error) { 46 | c, err := amqp.Dial(url) 47 | return &connWrapper{conn: c}, err 48 | } 49 | 50 | // Creates connection and trying to connect 51 | // use Close method to stop connection attempts 52 | func Open(url string, dial func(string) (Conner, error)) (Connecter, error) { 53 | instance := &Conn{url: url, dial: dial, done: make(chan struct{})} 54 | err := instance.conn() 55 | go func() { 56 | instance.recon() 57 | }() 58 | 59 | return instance, err 60 | } 61 | 62 | // IsConnected return connection status 63 | func (conn *Conn) IsConnected() bool { 64 | return atomic.LoadInt32(&conn.isConnected) == connected 65 | } 66 | 67 | // Close re-conn attempts 68 | func (conn *Conn) Close() error { 69 | conn.Lock() 70 | defer conn.Unlock() 71 | 72 | if atomic.LoadInt32(&conn.isConnected) == closed { 73 | return ErrClose 74 | } 75 | 76 | atomic.StoreInt32(&conn.isConnected, closed) 77 | close(conn.done) 78 | 79 | if conn.connection != nil { 80 | return conn.connection.Close() 81 | } 82 | 83 | return nil 84 | } 85 | 86 | func (conn *Conn) GetChannel() (*amqp.Channel, error) { 87 | if conn.IsConnected() { 88 | return conn.connection.GetChannel() 89 | } 90 | return nil, ErrConnectionUnavailable 91 | } 92 | 93 | func (conn *Conn) recon() { 94 | for { 95 | for { 96 | if atomic.LoadInt32(&conn.isConnected) == connected { 97 | break 98 | } 99 | 100 | if atomic.LoadInt32(&conn.isConnected) == closed { 101 | return 102 | } 103 | 104 | if err := conn.conn(); err != nil { 105 | if conn.attempts < maxAttempts { 106 | conn.attempts++ 107 | } 108 | } else { 109 | conn.attempts = 0 110 | } 111 | 112 | time.Sleep(time.Second * time.Duration(conn.attempts)) 113 | } 114 | 115 | select { 116 | case <-conn.done: 117 | return 118 | case <-conn.notifyClose: 119 | if atomic.LoadInt32(&conn.isConnected) != closed { 120 | atomic.StoreInt32(&conn.isConnected, disconnected) 121 | } 122 | } 123 | } 124 | } 125 | 126 | func (conn *Conn) conn() error { 127 | conn.Lock() 128 | defer conn.Unlock() 129 | 130 | c, err := conn.dial(conn.url) 131 | if err != nil { 132 | return err 133 | } 134 | 135 | conn.connection = c 136 | conn.notifyClose = make(chan *amqp.Error) 137 | conn.connection.NotifyClose(conn.notifyClose) 138 | 139 | atomic.StoreInt32(&conn.isConnected, connected) 140 | 141 | return nil 142 | } -------------------------------------------------------------------------------- /connector_test.go: -------------------------------------------------------------------------------- 1 | package rmqconn 2 | 3 | import ( 4 | "fmt" 5 | "github.com/streadway/amqp" 6 | "sync/atomic" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | var ( 12 | attempts int32 13 | ) 14 | 15 | type connMock struct { 16 | с chan *amqp.Error 17 | } 18 | 19 | func (c *connMock) Close() error { 20 | close(c.с) 21 | return nil 22 | } 23 | 24 | func (c *connMock) NotifyClose(ch chan *amqp.Error) chan *amqp.Error { 25 | c.с = ch 26 | return nil 27 | } 28 | 29 | func (c *connMock) GetChannel() (*amqp.Channel, error) { 30 | return new(amqp.Channel), nil 31 | } 32 | 33 | func (c *connMock) Disconnect() { 34 | close(c.с) 35 | } 36 | 37 | func DialMock(url string) (Conner, error) { 38 | defer atomic.AddInt32(&attempts, 1) 39 | 40 | if atomic.LoadInt32(&attempts) == 0 { 41 | conn := &connMock{make(chan *amqp.Error)} 42 | go func() { 43 | time.Sleep(time.Millisecond * 100) 44 | conn.Disconnect() 45 | }() 46 | return conn, nil 47 | } 48 | 49 | if atomic.LoadInt32(&attempts) <= 1 { 50 | return nil, fmt.Errorf("dial err") 51 | } else { 52 | return &connMock{make(chan *amqp.Error)}, nil 53 | } 54 | } 55 | 56 | func TestReconn(t *testing.T) { 57 | c, err := Open("amqp://host", DialMock) 58 | if err != nil { 59 | t.Fail() 60 | } 61 | if !c.IsConnected() { 62 | t.Fail() 63 | } else { 64 | _, err := c.GetChannel() 65 | if err != nil { 66 | t.Fail() 67 | } 68 | } 69 | 70 | time.Sleep(time.Second * 3) 71 | 72 | c.Close() 73 | c.Close() 74 | 75 | _, err = c.GetChannel() 76 | if err == nil { 77 | t.Fail() 78 | } 79 | } 80 | 81 | func TestDial(t *testing.T) { 82 | c, err := Open("amqp://host", Dial) 83 | if err == nil { 84 | t.Fail() 85 | } 86 | c.Close() 87 | } -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package rmqconn 2 | 3 | import ( 4 | "github.com/streadway/amqp" 5 | ) 6 | 7 | // Conner interface for wrapper Connection 8 | type Conner interface { 9 | Close() error 10 | NotifyClose(c chan *amqp.Error) chan *amqp.Error 11 | GetChannel() (*amqp.Channel, error) 12 | } 13 | 14 | // Connecter interface for connection instance 15 | type Connecter interface { 16 | GetChannel() (*amqp.Channel, error) 17 | IsConnected() bool 18 | Close() error 19 | } 20 | 21 | type connWrapper struct { 22 | conn *amqp.Connection 23 | } 24 | 25 | func (cw *connWrapper) Close() error { 26 | return cw.conn.Close() 27 | } 28 | 29 | func (cw *connWrapper) NotifyClose(c chan *amqp.Error) chan *amqp.Error { 30 | return cw.conn.NotifyClose(c) 31 | } 32 | 33 | func (cw *connWrapper) GetChannel() (*amqp.Channel, error) { 34 | return cw.conn.Channel() 35 | } --------------------------------------------------------------------------------