build: upgrade to go 1.17 and dependencies

This commit is contained in:
cyrilix committed 2021-09-01 22:13:52 +02:00
1 parent 0fe45a3c9c
commit fdedd70471
120 files changed
+10274 -1888

No files matched your search

+3 -2
View File
@@ -1,13 +1,14 @@
language: go
go:
- 1.6
- 1.7
- 1.8
- 1.9
- "1.10"
- 1.11
- 1.12
- 1.13
- 1.14
- 1.15
install:
- make setup
+33 -4
View File
@@ -64,6 +64,12 @@ nonintuitive interface (for me)
### BREAKING CHANGES
3.0.0
* `DelayTypeFunc` accepts a new parameter `err` - this breaking change affects
only your custom Delay Functions. This change allow [make delay functions based
on error](examples/delay_based_on_error_test.go).
1.0.2 -> 2.0.0
* argument of `retry.Delay` is final delay (no multiplication by `retry.Units`
@@ -91,13 +97,14 @@ var (
DefaultRetryIf = IsRecoverable
DefaultDelayType = CombineDelay(BackOffDelay, RandomDelay)
DefaultLastErrorOnly = false
DefaultContext = context.Background()
)
```
#### func BackOffDelay
```go
func BackOffDelay(n uint, config *Config) time.Duration
func BackOffDelay(n uint, _ error, config *Config) time.Duration
```
BackOffDelay is a DelayType which increases delay between consecutive retries
@@ -110,7 +117,7 @@ func Do(retryableFunc RetryableFunc, opts ...Option) error
#### func FixedDelay
```go
func FixedDelay(_ uint, config *Config) time.Duration
func FixedDelay(_ uint, _ error, config *Config) time.Duration
```
FixedDelay is a DelayType which keeps delay the same through all iterations
@@ -124,7 +131,7 @@ IsRecoverable checks if error is an instance of `unrecoverableError`
#### func RandomDelay
```go
func RandomDelay(_ uint, config *Config) time.Duration
func RandomDelay(_ uint, _ error, config *Config) time.Duration
```
RandomDelay is a DelayType which picks a random delay up to config.maxJitter
@@ -146,9 +153,11 @@ type Config struct {
#### type DelayTypeFunc
```go
type DelayTypeFunc func(n uint, config *Config) time.Duration
type DelayTypeFunc func(n uint, err error, config *Config) time.Duration
```
DelayTypeFunc is called to return the next delay to wait after the retriable
function fails on `err` after `n` attempts.
#### func CombineDelay
@@ -207,6 +216,26 @@ func Attempts(attempts uint) Option
```
Attempts set count of retry default is 10
#### func Context
```go
func Context(ctx context.Context) Option
```
Context allow to set context of retry default are Background context
example of immediately cancellation (maybe it isn't the best example, but it
describes behavior enough; I hope)
ctx, cancel := context.WithCancel(context.Background())
cancel()
retry.Do(
func() error {
...
},
retry.Context(ctx),
)
#### func Delay
```go
+1 -1
View File
@@ -1 +1 @@
2.6.0
3.0.0
+56 -9
View File
@@ -1,6 +1,8 @@
package retry
import (
"context"
"math"
"math/rand"
"time"
)
@@ -12,7 +14,8 @@ type RetryIfFunc func(error) bool
// n = count of attempts
type OnRetryFunc func(n uint, err error)
type DelayTypeFunc func(n uint, config *Config) time.Duration
// DelayTypeFunc is called to return the next delay to wait after the retriable function fails on `err` after `n` attempts.
type DelayTypeFunc func(n uint, err error, config *Config) time.Duration
type Config struct {
attempts uint
@@ -23,6 +26,9 @@ type Config struct {
retryIf RetryIfFunc
delayType DelayTypeFunc
lastErrorOnly bool
context context.Context
maxBackOffN uint
}
// Option represents an option for retry.
@@ -76,28 +82,49 @@ func DelayType(delayType DelayTypeFunc) Option {
}
// BackOffDelay is a DelayType which increases delay between consecutive retries
func BackOffDelay(n uint, config *Config) time.Duration {
return config.delay * (1 << n)
func BackOffDelay(n uint, _ error, config *Config) time.Duration {
// 1 << 63 would overflow signed int64 (time.Duration), thus 62.
const max uint = 62
if config.maxBackOffN == 0 {
if config.delay <= 0 {
config.delay = 1
}
config.maxBackOffN = max - uint(math.Floor(math.Log2(float64(config.delay))))
}
if n > config.maxBackOffN {
n = config.maxBackOffN
}
return config.delay << n
}
// FixedDelay is a DelayType which keeps delay the same through all iterations
func FixedDelay(_ uint, config *Config) time.Duration {
func FixedDelay(_ uint, _ error, config *Config) time.Duration {
return config.delay
}
// RandomDelay is a DelayType which picks a random delay up to config.maxJitter
func RandomDelay(_ uint, config *Config) time.Duration {
func RandomDelay(_ uint, _ error, config *Config) time.Duration {
return time.Duration(rand.Int63n(int64(config.maxJitter)))
}
// CombineDelay is a DelayType the combines all of the specified delays into a new DelayTypeFunc
func CombineDelay(delays ...DelayTypeFunc) DelayTypeFunc {
return func(n uint, config *Config) time.Duration {
var total time.Duration
const maxInt64 = uint64(math.MaxInt64)
return func(n uint, err error, config *Config) time.Duration {
var total uint64
for _, delay := range delays {
total += delay(n, config)
total += uint64(delay(n, err, config))
if total > maxInt64 {
total = maxInt64
}
}
return total
return time.Duration(total)
}
}
@@ -149,3 +176,23 @@ func RetryIf(retryIf RetryIfFunc) Option {
c.retryIf = retryIf
}
}
// Context allow to set context of retry
// default are Background context
//
// example of immediately cancellation (maybe it isn't the best example, but it describes behavior enough; I hope)
//
// ctx, cancel := context.WithCancel(context.Background())
// cancel()
//
// retry.Do(
// func() error {
// ...
// },
// retry.Context(ctx),
// )
func Context(ctx context.Context) Option {
return func(c *Config) {
c.context = ctx
}
}
+21 -2
View File
@@ -43,8 +43,14 @@ SEE ALSO
* [matryer/try](https://github.com/matryer/try) - very popular package, nonintuitive interface (for me)
BREAKING CHANGES
3.0.0
* `DelayTypeFunc` accepts a new parameter `err` - this breaking change affects only your custom Delay Functions. This change allow [make delay functions based on error](examples/delay_based_on_error_test.go).
1.0.2 -> 2.0.0
* argument of `retry.Delay` is final delay (no multiplication by `retry.Units` anymore)
@@ -65,6 +71,7 @@ BREAKING CHANGES
package retry
import (
"context"
"fmt"
"strings"
"time"
@@ -81,6 +88,7 @@ var (
DefaultRetryIf = IsRecoverable
DefaultDelayType = CombineDelay(BackOffDelay, RandomDelay)
DefaultLastErrorOnly = false
DefaultContext = context.Background()
)
func Do(retryableFunc RetryableFunc, opts ...Option) error {
@@ -95,6 +103,7 @@ func Do(retryableFunc RetryableFunc, opts ...Option) error {
retryIf: DefaultRetryIf,
delayType: DefaultDelayType,
lastErrorOnly: DefaultLastErrorOnly,
context: DefaultContext,
}
//apply opts
@@ -102,6 +111,10 @@ func Do(retryableFunc RetryableFunc, opts ...Option) error {
opt(config)
}
if err := config.context.Err(); err != nil {
return err
}
var errorLog Error
if !config.lastErrorOnly {
errorLog = make(Error, config.attempts)
@@ -127,11 +140,17 @@ func Do(retryableFunc RetryableFunc, opts ...Option) error {
break
}
delayTime := config.delayType(n, config)
delayTime := config.delayType(n, err, config)
if config.maxDelay > 0 && delayTime > config.maxDelay {
delayTime = config.maxDelay
}
time.Sleep(delayTime)
select {
case <-time.After(delayTime):
case <-config.context.Done():
return config.context.Err()
}
} else {
return nil
}
+3 -3
View File
@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0-devel
// protoc-gen-go v1.26.0
// protoc v3.12.4
// source: events/events.proto
@@ -966,8 +966,8 @@ var file_events_events_proto_rawDesc = []byte{
0x50, 0x49, 0x4c, 0x4f, 0x54, 0x10, 0x02, 0x2a, 0x32, 0x0a, 0x0a, 0x54, 0x79, 0x70, 0x65, 0x4f,
0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4e, 0x59, 0x10, 0x00, 0x12, 0x07,
0x0a, 0x03, 0x43, 0x41, 0x52, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x42, 0x55, 0x4d, 0x50, 0x10,
0x02, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4c, 0x4f, 0x54, 0x10, 0x03, 0x42, 0x08, 0x5a, 0x06, 0x65,
0x76, 0x65, 0x6e, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x02, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4c, 0x4f, 0x54, 0x10, 0x03, 0x42, 0x0a, 0x5a, 0x08, 0x2e,
0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
+6 -2
View File
@@ -104,8 +104,12 @@ func main() {
* Seemingly random disconnections may be caused by another client connecting to the broker with the same client
identifier; this is as per the [spec](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc384800405).
* A `MessageHandler` (called when a new message is received) must not block. If you wish to perform a long-running task,
or publish a message, then please use a go routine (blocking in the handler is a common cause of unexpected `pingresp
* Unless ordered delivery of messages is essential (and you have configured your broker to support this e.g.
`max_inflight_messages=1` in mosquitto) then set `ClientOptions.SetOrderMatters(false)`. Doing so will avoid the
below issue (deadlocks due to blocking message handlers).
* A `MessageHandler` (called when a new message is received) must not block (unless
`ClientOptions.SetOrderMatters(false)` set). If you wish to perform a long-running task, or publish a message, then
please use a go routine (blocking in the handler is a common cause of unexpected `pingresp
not received, disconnecting` errors).
* When QOS1+ subscriptions have been created previously and you connect with `CleanSession` set to false it is possible that the broker will deliver retained
messages before `Subscribe` can be called. To process these messages either configure a handler with `AddRoute` or
+92 -20
View File
@@ -55,6 +55,8 @@ const (
// information can be found in their respective documentation.
// Numerous connection options may be specified by configuring a
// and then supplying a ClientOptions type.
// Implementations of Client must be safe for concurrent use by multiple
// goroutines
type Client interface {
// IsConnected returns a bool signifying whether
// the client is connected or not.
@@ -75,11 +77,21 @@ type Client interface {
// Returns a token to track delivery of the message to the broker
Publish(topic string, qos byte, retained bool, payload interface{}) Token
// Subscribe starts a new subscription. Provide a MessageHandler to be executed when
// a message is published on the topic provided, or nil for the default handler
// a message is published on the topic provided, or nil for the default handler.
//
// If options.OrderMatters is true (the default) then callback must not block or
// call functions within this package that may block (e.g. Publish) other than in
// a new go routine.
// callback must be safe for concurrent use by multiple goroutines.
Subscribe(topic string, qos byte, callback MessageHandler) Token
// SubscribeMultiple starts a new subscription for multiple topics. Provide a MessageHandler to
// be executed when a message is published on one of the topics provided, or nil for the
// default handler
// default handler.
//
// If options.OrderMatters is true (the default) then callback must not block or
// call functions within this package that may block (e.g. Publish) other than in
// a new go routine.
// callback must be safe for concurrent use by multiple goroutines.
SubscribeMultiple(filters map[string]byte, callback MessageHandler) Token
// Unsubscribe will end the subscription from each of the topics provided.
// Messages published to those topics from other clients will no longer be
@@ -87,7 +99,13 @@ type Client interface {
Unsubscribe(topics ...string) Token
// AddRoute allows you to add a handler for messages on a specific topic
// without making a subscription. For example having a different handler
// for parts of a wildcard subscription
// for parts of a wildcard subscription or for receiving retained messages
// upon connection (before Sub scribe can be processed).
//
// If options.OrderMatters is true (the default) then callback must not block or
// call functions within this package that may block (e.g. Publish) other than in
// a new go routine.
// callback must be safe for concurrent use by multiple goroutines.
AddRoute(topic string, callback MessageHandler)
// OptionsReader returns a ClientOptionsReader which is a copy of the clientoptions
// in use by the client.
@@ -95,6 +113,8 @@ type Client interface {
}
// client implements the Client interface
// clients are safe for concurrent use by multiple
// goroutines
type client struct {
lastSent atomic.Value // time.Time - the last time a packet was successfully sent to network
lastReceived atomic.Value // time.Time - the last time a packet was successfully received from network
@@ -153,6 +173,11 @@ func NewClient(o *ClientOptions) Client {
// AddRoute allows you to add a handler for messages on a specific topic
// without making a subscription. For example having a different handler
// for parts of a wildcard subscription
//
// If options.OrderMatters is true (the default) then callback must not block or
// call functions within this package that may block (e.g. Publish) other than in
// a new go routine.
// callback must be safe for concurrent use by multiple goroutines.
func (c *client) AddRoute(topic string, callback MessageHandler) {
if callback != nil {
c.msgRouter.addRoute(topic, callback)
@@ -354,8 +379,13 @@ func (c *client) attemptConnection() (net.Conn, byte, bool, error) {
cm := newConnectMsgFromOptions(&c.options, broker)
DEBUG.Println(CLI, "about to write new connect msg")
CONN:
tlsCfg := c.options.TLSConfig
if c.options.OnConnectAttempt != nil {
DEBUG.Println(CLI, "using custom onConnectAttempt handler...")
tlsCfg = c.options.OnConnectAttempt(broker, c.options.TLSConfig)
}
// Start by opening the network connection (tcp, tls, ws) etc
conn, err = openConnection(broker, c.options.TLSConfig, c.options.ConnectTimeout, c.options.HTTPHeaders, c.options.WebsocketOptions)
conn, err = openConnection(broker, tlsCfg, c.options.ConnectTimeout, c.options.HTTPHeaders, c.options.WebsocketOptions)
if err != nil {
ERROR.Println(CLI, err.Error())
WARN.Println(CLI, "failed to connect to broker, trying next")
@@ -372,7 +402,7 @@ func (c *client) attemptConnection() (net.Conn, byte, bool, error) {
// We may be have to attempt the connection with MQTT 3.1
if conn != nil {
conn.Close()
_ = conn.Close()
}
if !c.options.protocolVersionExplicit && protocolVersion == 4 { // try falling back to 3.1?
DEBUG.Println(CLI, "Trying reconnect using MQTT 3.1 protocol")
@@ -409,12 +439,22 @@ func (c *client) Disconnect(quiesce uint) {
dm := packets.NewControlPacket(packets.Disconnect).(*packets.DisconnectPacket)
dt := newToken(packets.Disconnect)
c.oboundP <- &PacketAndToken{p: dm, t: dt}
disconnectSent := false
select {
case c.oboundP <- &PacketAndToken{p: dm, t: dt}:
disconnectSent = true
case <-c.commsStopped:
WARN.Println("Disconnect packet could not be sent because comms stopped")
case <-time.After(time.Duration(quiesce) * time.Millisecond):
WARN.Println("Disconnect packet not sent due to timeout")
}
// wait for work to finish, or quiesce time consumed
DEBUG.Println(CLI, "calling WaitTimeout")
dt.WaitTimeout(time.Duration(quiesce) * time.Millisecond)
DEBUG.Println(CLI, "WaitTimeout done")
if disconnectSent {
DEBUG.Println(CLI, "calling WaitTimeout")
dt.WaitTimeout(time.Duration(quiesce) * time.Millisecond)
DEBUG.Println(CLI, "WaitTimeout done")
}
} else {
WARN.Println(CLI, "Disconnect() called but not connected (disconnected/reconnecting)")
c.setConnected(disconnected)
@@ -459,10 +499,13 @@ func (c *client) internalConnLost(err error) {
DEBUG.Println(CLI, "internalConnLost waiting on workers")
<-stopDone
DEBUG.Println(CLI, "internalConnLost workers stopped")
if c.options.CleanSession && !c.options.AutoReconnect {
// It is possible that Disconnect was called which led to this error so reconnection depends upon status
reconnect := c.options.AutoReconnect && c.connectionStatus() > connecting
if c.options.CleanSession && !reconnect {
c.messageIds.cleanUp()
}
if c.options.AutoReconnect {
if reconnect {
c.setConnected(reconnecting)
go c.reconnect()
} else {
@@ -476,8 +519,8 @@ func (c *client) internalConnLost(err error) {
}
}
// startCommsWorkers is called when the connection is up. It starts off all of the routines needed to process incoming and
// outgoing messages.
// startCommsWorkers is called when the connection is up.
// It starts off all of the routines needed to process incoming and outgoing messages.
// Returns true if the comms workers were started (i.e. they were not already running)
func (c *client) startCommsWorkers(conn net.Conn, inboundFromStore <-chan packets.ControlPacket) bool {
DEBUG.Println(CLI, "startCommsWorkers called")
@@ -564,7 +607,22 @@ func (c *client) startCommsWorkers(conn net.Conn, inboundFromStore <-chan packet
commsIncomingPub = nil
continue
}
incomingPubChan <- pub
// Care is needed here because an error elsewhere could trigger a deadlock
sendPubLoop:
for {
select {
case incomingPubChan <- pub:
break sendPubLoop
case err, ok := <-commsErrors:
if !ok { // commsErrors has been closed so we can ignore it
commsErrors = nil
continue
}
ERROR.Println(CLI, "Connect comms goroutine - error triggered during send Pub", err)
c.internalConnLost(err) // no harm in calling this if the connection is already down (or shutdown is in progress)
continue
}
}
case err, ok := <-commsErrors:
if !ok {
commsErrors = nil
@@ -686,10 +744,10 @@ func (c *client) Publish(topic string, qos byte, retained bool, payload interfac
// Subscribe starts a new subscription. Provide a MessageHandler to be executed when
// a message is published on the topic provided.
//
// Please note: you should try to keep the execution time of the callback to be
// as low as possible, especially when SetOrderMatters(true) (the default) is in
// place. Blocking calls in message handlers might otherwise delay delivery to
// other message handlers.
// If options.OrderMatters is true (the default) then callback must not block or
// call functions within this package that may block (e.g. Publish) other than in
// a new go routine.
// callback must be safe for concurrent use by multiple goroutines.
func (c *client) Subscribe(topic string, qos byte, callback MessageHandler) Token {
token := newToken(packets.Subscribe).(*SubscribeToken)
DEBUG.Println(CLI, "enter Subscribe")
@@ -766,6 +824,11 @@ func (c *client) Subscribe(topic string, qos byte, callback MessageHandler) Toke
// SubscribeMultiple starts a new subscription for multiple topics. Provide a MessageHandler to
// be executed when a message is published on one of the topics provided.
//
// If options.OrderMatters is true (the default) then callback must not block or
// call functions within this package that may block (e.g. Publish) other than in
// a new go routine.
// callback must be safe for concurrent use by multiple goroutines.
func (c *client) SubscribeMultiple(filters map[string]byte, callback MessageHandler) Token {
var err error
token := newToken(packets.Subscribe).(*SubscribeToken)
@@ -869,7 +932,7 @@ func (c *client) resume(subscription bool, ibound chan packets.ControlPacket) {
}
details := packet.Details()
if isKeyOutbound(key) {
switch packet.(type) {
switch p := packet.(type) {
case *packets.SubscribePacket:
if subscription {
DEBUG.Println(STR, fmt.Sprintf("loaded pending subscribe (%d)", details.MessageID))
@@ -909,13 +972,22 @@ func (c *client) resume(subscription bool, ibound chan packets.ControlPacket) {
return
}
case *packets.PublishPacket:
// spec: If the DUP flag is set to 0, it indicates that this is the first occasion that the Client or
// Server has attempted to send this MQTT PUBLISH Packet. If the DUP flag is set to 1, it indicates that
// this might be re-delivery of an earlier attempt to send the Packet.
//
// If the message is in the store than an attempt at delivery has been made (note that the message may
// never have made it onto the wire but tracking that would be complicated!).
if p.Qos != 0 { // spec: The DUP flag MUST be set to 0 for all QoS 0 messages
p.Dup = true
}
token := newToken(packets.Publish).(*PublishToken)
token.messageID = details.MessageID
c.claimID(token, details.MessageID)
DEBUG.Println(STR, fmt.Sprintf("loaded pending publish (%d)", details.MessageID))
DEBUG.Println(STR, details)
select {
case c.obound <- &PacketAndToken{p: packet, t: token}:
case c.obound <- &PacketAndToken{p: p, t: token}:
case <-c.stop:
DEBUG.Println(STR, "resume exiting due to stop")
return
-8
View File
@@ -1,8 +0,0 @@
module github.com/eclipse/paho.mqtt.golang
go 1.14
require (
github.com/gorilla/websocket v1.4.2
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0
)
-8
View File
@@ -1,8 +0,0 @@
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U=
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+3 -2
View File
@@ -30,7 +30,8 @@ import (
// This just establishes the network connection; once established the type of connection should be irrelevant
//
// openConnection opens a network connection using the protocol indicated in the URL. Does not carry out any MQTT specific handshakes
// openConnection opens a network connection using the protocol indicated in the URL.
// Does not carry out any MQTT specific handshakes.
func openConnection(uri *url.URL, tlsc *tls.Config, timeout time.Duration, headers http.Header, websocketOptions *WebsocketOptions) (net.Conn, error) {
switch uri.Scheme {
case "ws":
@@ -81,7 +82,7 @@ func openConnection(uri *url.URL, tlsc *tls.Config, timeout time.Duration, heade
err = tlsConn.Handshake()
if err != nil {
conn.Close()
_ = conn.Close()
return nil, err
}
+26 -6
View File
@@ -21,7 +21,6 @@ import (
"crypto/tls"
"net/http"
"net/url"
"regexp"
"strings"
"time"
)
@@ -50,7 +49,11 @@ type OnConnectHandler func(Client)
// the initial connection is lost
type ReconnectHandler func(Client, *ClientOptions)
// ClientOptions contains configurable options for an Client.
// ConnectionAttemptHandler is invoked prior to making the initial connection.
type ConnectionAttemptHandler func(broker *url.URL, tlsCfg *tls.Config) *tls.Config
// ClientOptions contains configurable options for an Client. Note that these should be set using the
// relevant methods (e.g. AddBroker) rather than directly. See those functions for information on usage.
type ClientOptions struct {
Servers []*url.URL
ClientID string
@@ -79,6 +82,7 @@ type ClientOptions struct {
OnConnect OnConnectHandler
OnConnectionLost ConnectionLostHandler
OnReconnecting ReconnectHandler
OnConnectAttempt ConnectionAttemptHandler
WriteTimeout time.Duration
MessageChannelDepth uint
ResumeSubs bool
@@ -90,7 +94,7 @@ type ClientOptions struct {
// default values.
// Port: 1883
// CleanSession: True
// Order: True
// Order: True (note: it is recommended that this be set to FALSE unless order is important)
// KeepAlive: 30 (seconds)
// ConnectTimeout: 30 (seconds)
// MaxReconnectInterval 10 (minutes)
@@ -120,6 +124,7 @@ func NewClientOptions() *ClientOptions {
Store: nil,
OnConnect: nil,
OnConnectionLost: DefaultConnectionLostHandler,
OnConnectAttempt: nil,
WriteTimeout: 0, // 0 represents timeout disabled
ResumeSubs: false,
HTTPHeaders: make(map[string][]string),
@@ -137,14 +142,12 @@ func NewClientOptions() *ClientOptions {
//
// An example broker URI would look like: tcp://foobar.com:1883
func (o *ClientOptions) AddBroker(server string) *ClientOptions {
re := regexp.MustCompile(`%(25)?`)
if len(server) > 0 && server[0] == ':' {
server = "127.0.0.1" + server
}
if !strings.Contains(server, "://") {
server = "tcp://" + server
}
server = re.ReplaceAllLiteralString(server, "%25")
brokerURI, err := url.Parse(server)
if err != nil {
ERROR.Println(CLI, "Failed to parse %q broker address: %s", server, err)
@@ -206,10 +209,13 @@ func (o *ClientOptions) SetCleanSession(clean bool) *ClientOptions {
}
// SetOrderMatters will set the message routing to guarantee order within
// each QoS level. By default, this value is true. If set to false,
// each QoS level. By default, this value is true. If set to false (recommended),
// this flag indicates that messages can be delivered asynchronously
// from the client to the application and possibly arrive out of order.
// Specifically, the message handler is called in its own go routine.
// Note that setting this to true does not guarantee in-order delivery
// (this is subject to broker settings like "max_inflight_messages=1" in mosquitto)
// and if true then handlers must not block.
func (o *ClientOptions) SetOrderMatters(order bool) *ClientOptions {
o.Order = order
return o
@@ -289,6 +295,11 @@ func (o *ClientOptions) SetBinaryWill(topic string, payload []byte, qos byte, re
// SetDefaultPublishHandler sets the MessageHandler that will be called when a message
// is received that does not match any known subscriptions.
//
// If OrderMatters is true (the defaultHandler) then callback must not block or
// call functions within this package that may block (e.g. Publish) other than in
// a new go routine.
// defaultHandler must be safe for concurrent use by multiple goroutines.
func (o *ClientOptions) SetDefaultPublishHandler(defaultHandler MessageHandler) *ClientOptions {
o.DefaultPublishHandler = defaultHandler
return o
@@ -315,6 +326,15 @@ func (o *ClientOptions) SetReconnectingHandler(cb ReconnectHandler) *ClientOptio
return o
}
// SetConnectionAttemptHandler sets the ConnectionAttemptHandler callback to be executed prior
// to each attempt to connect to an MQTT broker. Returns the *tls.Config that will be used when establishing
// the connection (a copy of the tls.Config from ClientOptions will be passed in along with the broker URL).
// This allows connection specific changes to be made to the *tls.Config.
func (o *ClientOptions) SetConnectionAttemptHandler(onConnectAttempt ConnectionAttemptHandler) *ClientOptions {
o.OnConnectAttempt = onConnectAttempt
return o
}
// SetWriteTimeout puts a limit on how long a mqtt publish should block until it unblocks with a
// timeout error. A duration of 0 never times out. Default never times out
func (o *ClientOptions) SetWriteTimeout(t time.Duration) *ClientOptions {
+5 -1
View File
@@ -29,7 +29,11 @@ type ConnectPacket struct {
}
func (c *ConnectPacket) String() string {
return fmt.Sprintf("%s protocolversion: %d protocolname: %s cleansession: %t willflag: %t WillQos: %d WillRetain: %t Usernameflag: %t Passwordflag: %t keepalive: %d clientId: %s willtopic: %s willmessage: %s Username: %s Password: %s", c.FixedHeader, c.ProtocolVersion, c.ProtocolName, c.CleanSession, c.WillFlag, c.WillQos, c.WillRetain, c.UsernameFlag, c.PasswordFlag, c.Keepalive, c.ClientIdentifier, c.WillTopic, c.WillMessage, c.Username, c.Password)
var password string
if len(c.Password) > 0 {
password = "<redacted>"
}
return fmt.Sprintf("%s protocolversion: %d protocolname: %s cleansession: %t willflag: %t WillQos: %d WillRetain: %t Usernameflag: %t Passwordflag: %t keepalive: %d clientId: %s willtopic: %s willmessage: %s Username: %s Password: %s", c.FixedHeader, c.ProtocolVersion, c.ProtocolName, c.CleanSession, c.WillFlag, c.WillQos, c.WillRetain, c.UsernameFlag, c.PasswordFlag, c.Keepalive, c.ClientIdentifier, c.WillTopic, c.WillMessage, c.Username, password)
}
func (c *ConnectPacket) Write(w io.Writer) error {
+17 -7
View File
@@ -81,17 +81,27 @@ var ConnackReturnCodes = map[uint8]string{
255: "Connection Refused: Protocol Violation",
}
var (
ErrorRefusedBadProtocolVersion = errors.New("unacceptable protocol version")
ErrorRefusedIDRejected = errors.New("identifier rejected")
ErrorRefusedServerUnavailable = errors.New("server Unavailable")
ErrorRefusedBadUsernameOrPassword = errors.New("bad user name or password")
ErrorRefusedNotAuthorised = errors.New("not Authorized")
ErrorNetworkError = errors.New("network Error")
ErrorProtocolViolation = errors.New("protocol Violation")
)
// ConnErrors is a map of the errors codes constants for Connect()
// to a Go error
var ConnErrors = map[byte]error{
Accepted: nil,
ErrRefusedBadProtocolVersion: errors.New("unacceptable protocol version"),
ErrRefusedIDRejected: errors.New("identifier rejected"),
ErrRefusedServerUnavailable: errors.New("server Unavailable"),
ErrRefusedBadUsernameOrPassword: errors.New("bad user name or password"),
ErrRefusedNotAuthorised: errors.New("not Authorized"),
ErrNetworkError: errors.New("network Error"),
ErrProtocolViolation: errors.New("protocol Violation"),
ErrRefusedBadProtocolVersion: ErrorRefusedBadProtocolVersion,
ErrRefusedIDRejected: ErrorRefusedIDRejected,
ErrRefusedServerUnavailable: ErrorRefusedServerUnavailable,
ErrRefusedBadUsernameOrPassword: ErrorRefusedBadUsernameOrPassword,
ErrRefusedNotAuthorised: ErrorRefusedNotAuthorised,
ErrNetworkError: ErrorNetworkError,
ErrProtocolViolation: ErrorProtocolViolation,
}
// ReadPacket takes an instance of an io.Reader (such as net.Conn) and attempts
+52 -5
View File
@@ -132,13 +132,46 @@ func (r *router) setDefaultHandler(handler MessageHandler) {
// associated callback (or the defaultHandler, if one exists and no other route matched). If
// anything is sent down the stop channel the function will end.
func (r *router) matchAndDispatch(messages <-chan *packets.PublishPacket, order bool, client *client) <-chan *PacketAndToken {
ackChan := make(chan *PacketAndToken)
go func() {
var wg sync.WaitGroup
ackOutChan := make(chan *PacketAndToken) // Channel returned to caller; closed when messages channel closed
var ackInChan chan *PacketAndToken // ACKs generated by ackFunc get put onto this channel
stopAckCopy := make(chan struct{}) // Closure requests stop of go routine copying ackInChan to ackOutChan
ackCopyStopped := make(chan struct{}) // Closure indicates that it is safe to close ackOutChan
goRoutinesDone := make(chan struct{}) // closed on wg.Done()
if order {
ackInChan = ackOutChan // When order = true no go routines are used so safe to use one channel and close when done
} else {
// When order = false ACK messages are sent in go routines so ackInChan cannot be closed until all goroutines done
ackInChan = make(chan *PacketAndToken)
go func() { // go routine to copy from ackInChan to ackOutChan until stopped
for {
select {
case a := <-ackInChan:
ackOutChan <- a
case <-stopAckCopy:
close(ackCopyStopped) // Signal main go routine that it is safe to close ackOutChan
for {
select {
case <-ackInChan: // drain ackInChan to ensure all goRoutines can complete cleanly (ACK dropped)
DEBUG.Println(ROU, "matchAndDispatch received acknowledgment after processing stopped (ACK dropped).")
case <-goRoutinesDone:
close(ackInChan) // Nothing further should be sent (a panic is probably better than silent failure)
DEBUG.Println(ROU, "matchAndDispatch order=false copy goroutine exiting.")
return
}
}
}
}
}()
}
go func() { // Main go routine handling inbound messages
for message := range messages {
// DEBUG.Println(ROU, "matchAndDispatch received message")
sent := false
r.RLock()
m := messageFromPublish(message, ackFunc(ackChan, client.persist, message))
m := messageFromPublish(message, ackFunc(ackInChan, client.persist, message))
var handlers []MessageHandler
for e := r.routes.Front(); e != nil; e = e.Next() {
if e.Value.(*route).match(message.TopicName) {
@@ -146,9 +179,11 @@ func (r *router) matchAndDispatch(messages <-chan *packets.PublishPacket, order
handlers = append(handlers, e.Value.(*route).callback)
} else {
hd := e.Value.(*route).callback
wg.Add(1)
go func() {
hd(client, m)
m.Ack()
wg.Done()
}()
}
sent = true
@@ -159,9 +194,11 @@ func (r *router) matchAndDispatch(messages <-chan *packets.PublishPacket, order
if order {
handlers = append(handlers, r.defaultHandler)
} else {
wg.Add(1)
go func() {
r.defaultHandler(client, m)
m.Ack()
wg.Done()
}()
}
} else {
@@ -175,8 +212,18 @@ func (r *router) matchAndDispatch(messages <-chan *packets.PublishPacket, order
}
// DEBUG.Println(ROU, "matchAndDispatch handled message")
}
close(ackChan)
if order {
close(ackOutChan)
} else { // Ensure that nothing further will be written to ackOutChan before closing it
close(stopAckCopy)
<-ackCopyStopped
close(ackOutChan)
go func() {
wg.Wait() // Note: If this remains running then the user has handlers that are not returning
close(goRoutinesDone)
}()
}
DEBUG.Println(ROU, "matchAndDispatch exiting")
}()
return ackChan
return ackOutChan
}
+13 -3
View File
@@ -2,9 +2,11 @@ package mqtt
import (
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"net/url"
"sync"
"time"
@@ -15,8 +17,11 @@ import (
type WebsocketOptions struct {
ReadBufferSize int
WriteBufferSize int
Proxy ProxyFunction
}
type ProxyFunction func(req *http.Request) (*url.URL, error)
// NewWebsocket returns a new websocket and returns a net.Conn compatible interface using the gorilla/websocket package
func NewWebsocket(host string, tlsc *tls.Config, timeout time.Duration, requestHeader http.Header, options *WebsocketOptions) (net.Conn, error) {
if timeout == 0 {
@@ -27,9 +32,11 @@ func NewWebsocket(host string, tlsc *tls.Config, timeout time.Duration, requestH
// Apply default options
options = &WebsocketOptions{}
}
if options.Proxy == nil {
options.Proxy = http.ProxyFromEnvironment
}
dialer := &websocket.Dialer{
Proxy: http.ProxyFromEnvironment,
Proxy: options.Proxy,
HandshakeTimeout: timeout,
EnableCompression: false,
TLSClientConfig: tlsc,
@@ -38,9 +45,12 @@ func NewWebsocket(host string, tlsc *tls.Config, timeout time.Duration, requestH
WriteBufferSize: options.WriteBufferSize,
}
ws, _, err := dialer.Dial(host, requestHeader)
ws, resp, err := dialer.Dial(host, requestHeader)
if err != nil {
if resp != nil {
WARN.Println(CLI, fmt.Sprintf("Websocket handshake failure. StatusCode: %d. Body: %s", resp.StatusCode, resp.Body))
}
return nil, err
}
+2 -8
View File
@@ -13,6 +13,7 @@ import (
"strings"
"sync"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/runtime/protoimpl"
@@ -62,14 +63,7 @@ func FileDescriptor(s filePath) fileDescGZIP {
// Find the descriptor in the v2 registry.
var b []byte
if fd, _ := protoregistry.GlobalFiles.FindFileByPath(s); fd != nil {
if fd, ok := fd.(interface{ ProtoLegacyRawDesc() []byte }); ok {
b = fd.ProtoLegacyRawDesc()
} else {
// TODO: Use protodesc.ToFileDescriptorProto to construct
// a descriptorpb.FileDescriptorProto and marshal it.
// However, doing so causes the proto package to have a dependency
// on descriptorpb, leading to cyclic dependency issues.
}
b, _ = Marshal(protodesc.ToFileDescriptorProto(fd))
}
// Locally cache the raw descriptor form for the file.
-3
View File
@@ -1,3 +0,0 @@
module github.com/gorilla/websocket
go 1.12
View File
Whitespace-only changes.
+6 -8
View File
@@ -4,14 +4,12 @@ git:
depth: 1
env:
- GO111MODULE=on
go: [1.13.x, 1.14.x]
os: [linux, osx]
go: 1.15.x
os: linux
install:
- ./travis/install.sh
script:
- ./travis/cross_build.sh
- ./travis/lint.sh
- export GOMAXPROCS=4
- export GORACE=halt_on_error=1
- go test -race -v ./...
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then go test -race -v -tags appengine ./... ; fi
- cd ci
- go run mage.go -v -w ../ crossBuild
- go run mage.go -v -w ../ lint
- go run mage.go -v -w ../ test
+36
View File
@@ -1,3 +1,39 @@
# 1.8.1
Code quality:
* move magefile in its own subdir/submodule to remove magefile dependency on logrus consumer
* improve timestamp format documentation
Fixes:
* fix race condition on logger hooks
# 1.8.0
Correct versioning number replacing v1.7.1.
# 1.7.1
Beware this release has introduced a new public API and its semver is therefore incorrect.
Code quality:
* use go 1.15 in travis
* use magefile as task runner
Fixes:
* small fixes about new go 1.13 error formatting system
* Fix for long time race condiction with mutating data hooks
Features:
* build support for zos
# 1.7.0
Fixes:
* the dependency toward a windows terminal library has been removed
Features:
* a new buffer pool management API has been added
* a set of `<LogLevel>Fn()` functions have been added
# 1.6.0
Fixes:
* end of line cleanup
+1 -1
View File
@@ -402,7 +402,7 @@ func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
// source of the official loggers.
serialized, err := json.Marshal(entry.Data)
if err != nil {
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
return nil, fmt.Errorf("Failed to marshal fields to JSON, %w", err)
}
return append(serialized, '\n'), nil
}
+42 -33
View File
@@ -78,6 +78,14 @@ func NewEntry(logger *Logger) *Entry {
}
}
func (entry *Entry) Dup() *Entry {
data := make(Fields, len(entry.Data))
for k, v := range entry.Data {
data[k] = v
}
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, Context: entry.Context, err: entry.err}
}
// Returns the bytes representation of this entry from the formatter.
func (entry *Entry) Bytes() ([]byte, error) {
return entry.Logger.Formatter.Format(entry)
@@ -123,11 +131,9 @@ func (entry *Entry) WithFields(fields Fields) *Entry {
for k, v := range fields {
isErrField := false
if t := reflect.TypeOf(v); t != nil {
switch t.Kind() {
case reflect.Func:
switch {
case t.Kind() == reflect.Func, t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Func:
isErrField = true
case reflect.Ptr:
isErrField = t.Elem().Kind() == reflect.Func
}
}
if isErrField {
@@ -212,68 +218,72 @@ func (entry Entry) HasCaller() (has bool) {
entry.Caller != nil
}
// This function is not declared with a pointer value because otherwise
// race conditions will occur when using multiple goroutines
func (entry Entry) log(level Level, msg string) {
func (entry *Entry) log(level Level, msg string) {
var buffer *bytes.Buffer
// Default to now, but allow users to override if they want.
//
// We don't have to worry about polluting future calls to Entry#log()
// with this assignment because this function is declared with a
// non-pointer receiver.
if entry.Time.IsZero() {
entry.Time = time.Now()
newEntry := entry.Dup()
if newEntry.Time.IsZero() {
newEntry.Time = time.Now()
}
entry.Level = level
entry.Message = msg
entry.Logger.mu.Lock()
if entry.Logger.ReportCaller {
entry.Caller = getCaller()
}
entry.Logger.mu.Unlock()
newEntry.Level = level
newEntry.Message = msg
entry.fireHooks()
newEntry.Logger.mu.Lock()
reportCaller := newEntry.Logger.ReportCaller
newEntry.Logger.mu.Unlock()
if reportCaller {
newEntry.Caller = getCaller()
}
newEntry.fireHooks()
buffer = getBuffer()
defer func() {
entry.Buffer = nil
newEntry.Buffer = nil
putBuffer(buffer)
}()
buffer.Reset()
entry.Buffer = buffer
newEntry.Buffer = buffer
entry.write()
newEntry.write()
entry.Buffer = nil
newEntry.Buffer = nil
// To avoid Entry#log() returning a value that only would make sense for
// panic() to use in Entry#Panic(), we avoid the allocation by checking
// directly here.
if level <= PanicLevel {
panic(&entry)
panic(newEntry)
}
}
func (entry *Entry) fireHooks() {
var tmpHooks LevelHooks
entry.Logger.mu.Lock()
defer entry.Logger.mu.Unlock()
err := entry.Logger.Hooks.Fire(entry.Level, entry)
tmpHooks = make(LevelHooks, len(entry.Logger.Hooks))
for k, v := range entry.Logger.Hooks {
tmpHooks[k] = v
}
entry.Logger.mu.Unlock()
err := tmpHooks.Fire(entry.Level, entry)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
}
}
func (entry *Entry) write() {
entry.Logger.mu.Lock()
defer entry.Logger.mu.Unlock()
serialized, err := entry.Logger.Formatter.Format(entry)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
return
}
if _, err = entry.Logger.Out.Write(serialized); err != nil {
entry.Logger.mu.Lock()
defer entry.Logger.mu.Unlock()
if _, err := entry.Logger.Out.Write(serialized); err != nil {
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
}
}
@@ -319,7 +329,6 @@ func (entry *Entry) Fatal(args ...interface{}) {
func (entry *Entry) Panic(args ...interface{}) {
entry.Log(PanicLevel, args...)
panic(fmt.Sprint(args...))
}
// Entry Printf family functions
-10
View File
@@ -1,10 +0,0 @@
module github.com/sirupsen/logrus
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.2.2
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037
)
go 1.13
-10
View File
@@ -1,10 +0,0 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+4 -1
View File
@@ -23,6 +23,9 @@ func (f FieldMap) resolve(key fieldKey) string {
// JSONFormatter formats logs into parsable json
type JSONFormatter struct {
// TimestampFormat sets the format used for marshaling timestamps.
// The format to use is the same than for time.Format or time.Parse from the standard
// library.
// The standard Library already provides a set of predefined format.
TimestampFormat string
// DisableTimestamp allows disabling automatic timestamps in output
@@ -118,7 +121,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
encoder.SetIndent("", " ")
}
if err := encoder.Encode(data); err != nil {
return nil, fmt.Errorf("failed to marshal fields to JSON, %v", err)
return nil, fmt.Errorf("failed to marshal fields to JSON, %w", err)
}
return b.Bytes(), nil
+1 -1
View File
@@ -12,7 +12,7 @@ import (
// LogFunction For big messages, it can be more efficient to pass a function
// and only call it if the log level is actually enables rather than
// generating the log message and then checking if the level is enabled
type LogFunction func()[]interface{}
type LogFunction func() []interface{}
type Logger struct {
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
+1 -1
View File
@@ -1,4 +1,4 @@
// +build linux aix
// +build linux aix zos
// +build !js
package logrus
+6 -1
View File
@@ -53,7 +53,10 @@ type TextFormatter struct {
// the time passed since beginning of execution.
FullTimestamp bool
// TimestampFormat to use for display when a full timestamp is printed
// TimestampFormat to use for display when a full timestamp is printed.
// The format to use is the same than for time.Format or time.Parse from the standard
// library.
// The standard Library already provides a set of predefined format.
TimestampFormat string
// The fields are sorted by default for a consistent output. For applications
@@ -235,6 +238,8 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
levelColor = yellow
case ErrorLevel, FatalLevel, PanicLevel:
levelColor = red
case InfoLevel:
levelColor = blue
default:
levelColor = blue
}