mirror of
https://github.com/go-mqtt/mqtt.git
synced 2025-08-08 22:42:05 +03:00
Error scenario tuning including new ErrBoke case.
This commit is contained in:
14
client.go
14
client.go
@@ -24,7 +24,7 @@ var readBufSize = 128 * 1024
|
|||||||
var ErrDown = errors.New("mqtt: connection unavailable")
|
var ErrDown = errors.New("mqtt: connection unavailable")
|
||||||
|
|
||||||
// ErrClosed signals use after Close. The state is permanent.
|
// ErrClosed signals use after Close. The state is permanent.
|
||||||
// Further invocation will again result in the same error.
|
// Further invocation will result again in an ErrClosed error.
|
||||||
var ErrClosed = errors.New("mqtt: client closed")
|
var ErrClosed = errors.New("mqtt: client closed")
|
||||||
|
|
||||||
// ErrBrokerTerm signals connection loss for unknown reasons.
|
// ErrBrokerTerm signals connection loss for unknown reasons.
|
||||||
@@ -462,12 +462,12 @@ func (c *Client) termCallbacks() {
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
c.unorderedTxs.close()
|
c.unorderedTxs.breakAll()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case ack := <-c.pingAck:
|
case ack := <-c.pingAck:
|
||||||
ack <- ErrClosed
|
ack <- ErrBreak
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -537,6 +537,14 @@ func (c *Client) toOffline() {
|
|||||||
default:
|
default:
|
||||||
c.onlineSig <- on
|
c.onlineSig <- on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case ack := <-c.pingAck:
|
||||||
|
ack <- ErrBreak
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
c.unorderedTxs.breakAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) lockWrite(quit <-chan struct{}) (net.Conn, error) {
|
func (c *Client) lockWrite(quit <-chan struct{}) (net.Conn, error) {
|
||||||
|
@@ -282,45 +282,37 @@ func TestDown(t *testing.T) {
|
|||||||
if !errors.Is(err, mqtt.ErrDown) {
|
if !errors.Is(err, mqtt.ErrDown) {
|
||||||
t.Errorf("PublishRetained round %d got error %q, want an ErrDown", roundN, err)
|
t.Errorf("PublishRetained round %d got error %q, want an ErrDown", roundN, err)
|
||||||
}
|
}
|
||||||
ack, err := client.PublishAtLeastOnce(nil, "x")
|
_, err = client.PublishAtLeastOnce(nil, "x")
|
||||||
if roundN > 1 {
|
if roundN > 1 {
|
||||||
if !errors.Is(err, mqtt.ErrMax) {
|
if !errors.Is(err, mqtt.ErrMax) {
|
||||||
t.Errorf("PublishAtLeastOnce round %d got error %q, want an ErrMax", roundN, err)
|
t.Errorf("PublishAtLeastOnce round %d got error %q, want an ErrMax", roundN, err)
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
t.Errorf("PublishAtLeastOnce round %d got error %q", roundN, err)
|
t.Errorf("PublishAtLeastOnce round %d got error %q", roundN, err)
|
||||||
} else if err := <-ack; !errors.Is(err, mqtt.ErrDown) {
|
|
||||||
t.Errorf("PublishAtLeastOnce round %d ack got error %q, want an ErrDown", roundN, err)
|
|
||||||
}
|
}
|
||||||
ack, err = client.PublishAtLeastOnceRetained(nil, "x")
|
_, err = client.PublishAtLeastOnceRetained(nil, "x")
|
||||||
if roundN > 1 {
|
if roundN > 1 {
|
||||||
if !errors.Is(err, mqtt.ErrMax) {
|
if !errors.Is(err, mqtt.ErrMax) {
|
||||||
t.Errorf("PublishAtLeastOnceRetained round %d got error %q, want an ErrMax", roundN, err)
|
t.Errorf("PublishAtLeastOnceRetained round %d got error %q, want an ErrMax", roundN, err)
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
t.Errorf("PublishAtLeastOnceRetained round %d got error %q", roundN, err)
|
t.Errorf("PublishAtLeastOnceRetained round %d got error %q", roundN, err)
|
||||||
} else if err := <-ack; !errors.Is(err, mqtt.ErrDown) {
|
|
||||||
t.Errorf("PublishAtLeastOnceRetained round %d ack got error %q, want an ErrDown", roundN, err)
|
|
||||||
}
|
}
|
||||||
ack, err = client.PublishExactlyOnce(nil, "x")
|
_, err = client.PublishExactlyOnce(nil, "x")
|
||||||
if roundN > 1 {
|
if roundN > 1 {
|
||||||
if !errors.Is(err, mqtt.ErrMax) {
|
if !errors.Is(err, mqtt.ErrMax) {
|
||||||
t.Errorf("PublishExactlyOnce round %d got error %q, want an ErrMax", roundN, err)
|
t.Errorf("PublishExactlyOnce round %d got error %q, want an ErrMax", roundN, err)
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
t.Errorf("PublishExactlyOnce round %d got error %q", roundN, err)
|
t.Errorf("PublishExactlyOnce round %d got error %q", roundN, err)
|
||||||
} else if err := <-ack; !errors.Is(err, mqtt.ErrDown) {
|
|
||||||
t.Errorf("PublishExactlyOnce round %d ack got error %q, want an ErrDown", roundN, err)
|
|
||||||
}
|
}
|
||||||
ack, err = client.PublishExactlyOnceRetained(nil, "x")
|
_, err = client.PublishExactlyOnceRetained(nil, "x")
|
||||||
if roundN > 1 {
|
if roundN > 1 {
|
||||||
if !errors.Is(err, mqtt.ErrMax) {
|
if !errors.Is(err, mqtt.ErrMax) {
|
||||||
t.Errorf("PublishExactlyOnceRetained round %d got error %q, want an ErrMax", roundN, err)
|
t.Errorf("PublishExactlyOnceRetained round %d got error %q, want an ErrMax", roundN, err)
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
t.Errorf("PublishExactlyOnceRetained round %d got error %q", roundN, err)
|
t.Errorf("PublishExactlyOnceRetained round %d got error %q", roundN, err)
|
||||||
} else if err := <-ack; !errors.Is(err, mqtt.ErrDown) {
|
|
||||||
t.Errorf("PublishExactlyOnceRetained round %d ack got error %q, want an ErrDown", roundN, err)
|
|
||||||
}
|
}
|
||||||
err = client.Ping(nil)
|
err = client.Ping(nil)
|
||||||
if !errors.Is(err, mqtt.ErrDown) {
|
if !errors.Is(err, mqtt.ErrDown) {
|
||||||
|
121
example_test.go
121
example_test.go
@@ -29,18 +29,14 @@ var Online func() <-chan struct{}
|
|||||||
func init() {
|
func init() {
|
||||||
PublishAtLeastOnce = mqtttest.NewPublishAckStub(nil)
|
PublishAtLeastOnce = mqtttest.NewPublishAckStub(nil)
|
||||||
Subscribe = mqtttest.NewSubscribeStub(nil)
|
Subscribe = mqtttest.NewSubscribeStub(nil)
|
||||||
Online = func() <-chan struct{} {
|
Online = func() <-chan struct{} { return nil }
|
||||||
ch := make(chan struct{})
|
|
||||||
close(ch)
|
|
||||||
return ch
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// It is good practice to install the client from main.
|
// It is good practice to install the client from main.
|
||||||
func ExampleClient_setup() {
|
func ExampleClient_setup() {
|
||||||
client, err := mqtt.VolatileSession("demo-client", &mqtt.Config{
|
client, err := mqtt.VolatileSession("demo-client", &mqtt.Config{
|
||||||
Dialer: mqtt.NewDialer("tcp", "localhost:1883"),
|
Dialer: mqtt.NewDialer("tcp", "localhost:1883"),
|
||||||
WireTimeout: time.Second,
|
WireTimeout: 4 * time.Second,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("exit on broken setup: ", err)
|
log.Fatal("exit on broken setup: ", err)
|
||||||
@@ -50,27 +46,27 @@ func ExampleClient_setup() {
|
|||||||
go func() {
|
go func() {
|
||||||
var big *mqtt.BigMessage
|
var big *mqtt.BigMessage
|
||||||
for {
|
for {
|
||||||
message, channel, err := client.ReadSlices()
|
message, topic, err := client.ReadSlices()
|
||||||
switch {
|
switch {
|
||||||
case err == nil:
|
case err == nil:
|
||||||
// do something with inbound message
|
// do something with inbound message
|
||||||
log.Printf("📥 %q: %q", channel, message)
|
log.Printf("📥 %q: %q", topic, message)
|
||||||
|
|
||||||
|
case errors.As(err, &big):
|
||||||
|
log.Printf("📥 %q: %d byte message omitted", big.Topic, big.Size)
|
||||||
|
|
||||||
case errors.Is(err, mqtt.ErrClosed):
|
case errors.Is(err, mqtt.ErrClosed):
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
return // terminated
|
return // terminated
|
||||||
|
|
||||||
case errors.As(err, &big):
|
|
||||||
log.Printf("%d byte content skipped", big.Size)
|
|
||||||
|
|
||||||
case mqtt.IsConnectionRefused(err):
|
case mqtt.IsConnectionRefused(err):
|
||||||
log.Print(err)
|
log.Print("queue unavailable: ", err)
|
||||||
// ErrDown for a while
|
// ErrDown for a while
|
||||||
time.Sleep(15*time.Minute - time.Second)
|
time.Sleep(15 * time.Minute)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Print("MQTT unavailable: ", err)
|
log.Print("queue unavailable: ", err)
|
||||||
// ErrDown for short backoff
|
// ErrDown during backoff
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,83 +102,110 @@ func ExampleClient_setup() {
|
|||||||
// Output:
|
// Output:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error scenario and how to act uppon them.
|
// Demonstrates all error scenario and the respective recovery options.
|
||||||
func ExampleClient_PublishAtLeastOnce_hasty() {
|
func ExampleClient_PublishAtLeastOnce_critical() {
|
||||||
for {
|
for {
|
||||||
ack, err := PublishAtLeastOnce([]byte("🍸🆘"), "demo/alert")
|
exchange, err := PublishAtLeastOnce([]byte("🍸🆘"), "demo/alert")
|
||||||
switch {
|
switch {
|
||||||
case err == nil:
|
case err == nil:
|
||||||
fmt.Println("alert submitted")
|
fmt.Println("alert submitted…")
|
||||||
|
break
|
||||||
|
|
||||||
case mqtt.IsDeny(err), errors.Is(err, mqtt.ErrClosed):
|
case mqtt.IsDeny(err), errors.Is(err, mqtt.ErrClosed):
|
||||||
fmt.Println("🚨 alert not send:", err)
|
fmt.Println("🚨 alert not send:", err)
|
||||||
return
|
return
|
||||||
|
|
||||||
case errors.Is(err, mqtt.ErrDown):
|
|
||||||
fmt.Println("⚠️ alert delay:", err)
|
|
||||||
<-Online()
|
|
||||||
|
|
||||||
case errors.Is(err, mqtt.ErrMax):
|
case errors.Is(err, mqtt.ErrMax):
|
||||||
fmt.Println("⚠️ alert delay:", err)
|
fmt.Println("⚠️ alert submission hold-up:", err)
|
||||||
time.Sleep(time.Second / 4)
|
time.Sleep(time.Second / 4)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
default:
|
default:
|
||||||
fmt.Println("⚠️ alert delay on persistence malfunction:", err)
|
fmt.Println("⚠️ alert submission blocked on persistence malfunction:", err)
|
||||||
time.Sleep(time.Second)
|
time.Sleep(4 * time.Second)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for err := range ack {
|
for err := range exchange {
|
||||||
if errors.Is(err, mqtt.ErrClosed) {
|
if errors.Is(err, mqtt.ErrClosed) {
|
||||||
fmt.Println("🚨 alert suspended:", err)
|
fmt.Println("🚨 alert exchange suspended:", err)
|
||||||
// Submission will continue when the Client
|
// An AdoptSession may continue the transaction.
|
||||||
// is restarted with the same Store again.
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("⚠️ alert delay on connection malfunction:", err)
|
|
||||||
|
fmt.Println("⚠️ alert request transfer interupted:", err)
|
||||||
}
|
}
|
||||||
fmt.Println("alert confirmed")
|
fmt.Println("alert acknowledged ✓")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// alert submitted
|
// alert submitted…
|
||||||
// alert confirmed
|
// alert acknowledged ✓
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error scenario and how to act uppon them.
|
// Demonstrates all error scenario and the respective recovery options.
|
||||||
func ExampleClient_Subscribe_sticky() {
|
func ExampleClient_Subscribe_sticky() {
|
||||||
const topicFilter = "demo/+"
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
err := Subscribe(ctx.Done(), topicFilter)
|
err := Subscribe(ctx.Done(), "demo/+")
|
||||||
switch {
|
switch {
|
||||||
case err == nil:
|
case err == nil:
|
||||||
fmt.Printf("subscribed to %q", topicFilter)
|
fmt.Println("subscribe confirmed by broker")
|
||||||
return
|
return
|
||||||
|
|
||||||
case mqtt.IsDeny(err), errors.Is(err, mqtt.ErrClosed):
|
case errors.As(err, new(mqtt.SubscribeError)):
|
||||||
fmt.Print("no subscribe: ", err)
|
fmt.Println("subscribe failed by broker")
|
||||||
return
|
return
|
||||||
|
|
||||||
case errors.Is(err, mqtt.ErrCanceled), errors.Is(err, mqtt.ErrAbandoned):
|
case mqtt.IsDeny(err): // illegal topic filter
|
||||||
fmt.Print("subscribe timeout: ", err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
case errors.Is(err, mqtt.ErrClosed):
|
||||||
|
fmt.Println("no subscribe due client close")
|
||||||
|
return
|
||||||
|
|
||||||
|
case errors.Is(err, mqtt.ErrCanceled):
|
||||||
|
fmt.Println("no subscribe due timeout")
|
||||||
|
return
|
||||||
|
|
||||||
|
case errors.Is(err, mqtt.ErrAbandoned):
|
||||||
|
fmt.Println("subscribe state unknown due timeout")
|
||||||
|
return
|
||||||
|
|
||||||
|
case errors.Is(err, mqtt.ErrBreak):
|
||||||
|
fmt.Println("subscribe state unknown due connection loss")
|
||||||
|
select {
|
||||||
|
case <-Online():
|
||||||
|
fmt.Println("subscribe retry with new connection")
|
||||||
|
case <-ctx.Done():
|
||||||
|
fmt.Println("subscribe timeout")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
case errors.Is(err, mqtt.ErrDown):
|
case errors.Is(err, mqtt.ErrDown):
|
||||||
<-Online()
|
fmt.Println("subscribe delay while service is down")
|
||||||
|
select {
|
||||||
|
case <-Online():
|
||||||
|
fmt.Println("subscribe retry with new connection")
|
||||||
|
case <-ctx.Done():
|
||||||
|
fmt.Println("subscribe timeout")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
case errors.Is(err, mqtt.ErrMax):
|
case errors.Is(err, mqtt.ErrMax): // limit is quite high
|
||||||
time.Sleep(time.Second / 2)
|
fmt.Println("subscribe hold-up:", err)
|
||||||
|
time.Sleep(2 * time.Second) // backoff
|
||||||
|
|
||||||
default:
|
default:
|
||||||
backoff := 4 * time.Second
|
fmt.Println("subscribe request transfer interupted:", err)
|
||||||
fmt.Printf("subscribe retry in %s on: %s", backoff, err)
|
time.Sleep(time.Second / 2) // backoff
|
||||||
time.Sleep(backoff)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// subscribed to "demo/+"
|
// subscribe confirmed by broker
|
||||||
}
|
}
|
||||||
|
@@ -95,51 +95,51 @@ func NewPublishStub(returnFix error) func(quit <-chan struct{}, message []byte,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AckBlock prevents ack <-chan error submission.
|
// ExchangeBlock prevents exchange <-chan error submission.
|
||||||
type AckBlock struct {
|
type ExchangeBlock struct {
|
||||||
Delay time.Duration // zero defaults to indefinite
|
Delay time.Duration // zero defaults to indefinite
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error implements the standard error interface.
|
// Error implements the standard error interface.
|
||||||
func (b AckBlock) Error() string {
|
func (b ExchangeBlock) Error() string {
|
||||||
return "mqtttest: AckBlock used as an error"
|
return "mqtttest: ExchangeBlock used as an error"
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPublishAckStub returns a stub for mqtt.Client PublishAtLeastOnce or
|
// NewPublishEnqueuedStub returns a stub for mqtt.Client PublishAtLeastOnce or
|
||||||
// PublishExactlyOnce with a fixed return value.
|
// PublishExactlyOnce with a fixed return value.
|
||||||
//
|
//
|
||||||
// The ackFix errors are applied to the ack return, with an option for AckBlock
|
// The exchangeFix errors are applied to the exchange return, with an option for
|
||||||
// entries. An mqtt.ErrClosed in the ackFix keeps the ack channel open (without
|
// ExchangeBlock entries. An mqtt.ErrClosed in the exchangeFix keeps the
|
||||||
// an extra AckBlock entry.
|
// exchange channel open (without an extra ExchangeBlock entry).
|
||||||
func NewPublishAckStub(errFix error, ackFix ...error) func(message []byte, topic string) (ack <-chan error, err error) {
|
func NewPublishEnqueuedStub(errFix error, exchangeFix ...error) func(message []byte, topic string) (exchange <-chan error, err error) {
|
||||||
if errFix != nil && len(ackFix) != 0 {
|
if errFix != nil && len(exchangeFix) != 0 {
|
||||||
panic("ackFix entries with non-nil errFix")
|
panic("exchangeFix entries with non-nil errFix")
|
||||||
}
|
}
|
||||||
var block AckBlock
|
var block ExchangeBlock
|
||||||
for i, err := range ackFix {
|
for i, err := range exchangeFix {
|
||||||
switch {
|
switch {
|
||||||
case err == nil:
|
case err == nil:
|
||||||
panic("nil entry in ackFix")
|
panic("nil entry in exchangeFix")
|
||||||
case errors.Is(err, mqtt.ErrClosed):
|
case errors.Is(err, mqtt.ErrClosed):
|
||||||
if i+1 < len(ackFix) {
|
if i+1 < len(exchangeFix) {
|
||||||
panic("followup of mqtt.ErrClosed ackFix entry")
|
panic("followup on mqtt.ErrClosed exchangeFix entry")
|
||||||
}
|
}
|
||||||
case errors.As(err, &block):
|
case errors.As(err, &block):
|
||||||
if block.Delay == 0 && i+1 < len(ackFix) {
|
if block.Delay == 0 && i+1 < len(exchangeFix) {
|
||||||
panic("followup of indefinite AckBlock ackFix entry")
|
panic("followup on indefinite ExchangeBlock exchangeFix entry")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return func(message []byte, topic string) (ack <-chan error, err error) {
|
return func(message []byte, topic string) (exchange <-chan error, err error) {
|
||||||
if errFix != nil {
|
if errFix != nil {
|
||||||
return nil, errFix
|
return nil, errFix
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := make(chan error, len(ackFix))
|
ch := make(chan error, len(exchangeFix))
|
||||||
go func() {
|
go func() {
|
||||||
var block AckBlock
|
var block ExchangeBlock
|
||||||
for _, err := range ackFix {
|
for _, err := range exchangeFix {
|
||||||
switch {
|
switch {
|
||||||
default:
|
default:
|
||||||
ch <- err
|
ch <- err
|
||||||
|
@@ -13,7 +13,7 @@ var (
|
|||||||
subscribe = client.Subscribe
|
subscribe = client.Subscribe
|
||||||
unsubscribe = client.Unsubscribe
|
unsubscribe = client.Unsubscribe
|
||||||
publish = client.Publish
|
publish = client.Publish
|
||||||
publishAck = client.PublishAtLeastOnce
|
publishEnqueued = client.PublishAtLeastOnce
|
||||||
readSlices = client.ReadSlices
|
readSlices = client.ReadSlices
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,13 +23,13 @@ func TestSignatureMatch(t *testing.T) {
|
|||||||
// check dupe assumptions
|
// check dupe assumptions
|
||||||
subscribe = c.SubscribeLimitAtMostOnce
|
subscribe = c.SubscribeLimitAtMostOnce
|
||||||
subscribe = c.SubscribeLimitAtLeastOnce
|
subscribe = c.SubscribeLimitAtLeastOnce
|
||||||
publishAck = c.PublishExactlyOnce
|
publishEnqueued = c.PublishExactlyOnce
|
||||||
|
|
||||||
// check fits
|
// check fits
|
||||||
readSlices = mqtttest.NewReadSlicesMock(t)
|
readSlices = mqtttest.NewReadSlicesMock(t)
|
||||||
publish = mqtttest.NewPublishMock(t)
|
publish = mqtttest.NewPublishMock(t)
|
||||||
publish = mqtttest.NewPublishStub(nil)
|
publish = mqtttest.NewPublishStub(nil)
|
||||||
publishAck = mqtttest.NewPublishAckStub(nil)
|
publishEnqueued = mqtttest.NewPublishEnqueuedStub(nil)
|
||||||
subscribe = mqtttest.NewSubscribeMock(t)
|
subscribe = mqtttest.NewSubscribeMock(t)
|
||||||
subscribe = mqtttest.NewSubscribeStub(nil)
|
subscribe = mqtttest.NewSubscribeStub(nil)
|
||||||
unsubscribe = mqtttest.NewUnsubscribeMock(t)
|
unsubscribe = mqtttest.NewUnsubscribeMock(t)
|
||||||
|
44
request.go
44
request.go
@@ -20,9 +20,13 @@ var ErrMax = errors.New("mqtt: maximum number of pending requests reached")
|
|||||||
var ErrCanceled = errors.New("mqtt: request canceled before submission")
|
var ErrCanceled = errors.New("mqtt: request canceled before submission")
|
||||||
|
|
||||||
// ErrAbandoned means that a quit signal got applied after the request was send.
|
// ErrAbandoned means that a quit signal got applied after the request was send.
|
||||||
// The result remains unknown, as opposed to ErrCanceled.
|
// The broker received the request, yet the result/reponse remains unkown.
|
||||||
var ErrAbandoned = errors.New("mqtt: request abandoned after submission")
|
var ErrAbandoned = errors.New("mqtt: request abandoned after submission")
|
||||||
|
|
||||||
|
// ErrBreak means that the connection broke up after the request was send.
|
||||||
|
// The broker received the request, yet the result/reponse remains unkown.
|
||||||
|
var ErrBreak = errors.New("mqtt: connection lost while awaiting response")
|
||||||
|
|
||||||
// BufSize should fit topic names with a bit of overhead.
|
// BufSize should fit topic names with a bit of overhead.
|
||||||
const bufSize = 128
|
const bufSize = 128
|
||||||
|
|
||||||
@@ -187,12 +191,12 @@ func (txs *unorderedTxs) endTx(packetID uint16) (done chan<- error, topicFilters
|
|||||||
return callback.done, callback.topicFilters
|
return callback.done, callback.topicFilters
|
||||||
}
|
}
|
||||||
|
|
||||||
func (txs *unorderedTxs) close() {
|
func (txs *unorderedTxs) breakAll() {
|
||||||
txs.Lock()
|
txs.Lock()
|
||||||
defer txs.Unlock()
|
defer txs.Unlock()
|
||||||
for packetID, callback := range txs.perPacketID {
|
for packetID, callback := range txs.perPacketID {
|
||||||
delete(txs.perPacketID, packetID)
|
delete(txs.perPacketID, packetID)
|
||||||
callback.done <- ErrClosed
|
callback.done <- ErrBreak
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,9 +311,7 @@ func (c *Client) onSUBACK() error {
|
|||||||
return errProtoReset
|
return errProtoReset
|
||||||
}
|
}
|
||||||
|
|
||||||
if failN == 0 {
|
if failN != 0 {
|
||||||
close(done)
|
|
||||||
} else {
|
|
||||||
var err SubscribeError
|
var err SubscribeError
|
||||||
for i, code := range returnCodes {
|
for i, code := range returnCodes {
|
||||||
if code == 0x80 {
|
if code == 0x80 {
|
||||||
@@ -318,6 +320,7 @@ func (c *Client) onSUBACK() error {
|
|||||||
}
|
}
|
||||||
done <- err
|
done <- err
|
||||||
}
|
}
|
||||||
|
close(done)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,7 +415,7 @@ type holdup struct {
|
|||||||
// Publish delivers the message with an “at most once” guarantee.
|
// Publish delivers the message with an “at most once” guarantee.
|
||||||
// Subscribers may or may not receive the message when subject to error.
|
// Subscribers may or may not receive the message when subject to error.
|
||||||
// This delivery method is the most efficient option.
|
// This delivery method is the most efficient option.
|
||||||
///
|
//
|
||||||
// Quit is optional, as nil just blocks. Appliance of quit will strictly result
|
// Quit is optional, as nil just blocks. Appliance of quit will strictly result
|
||||||
// in ErrCanceled.
|
// in ErrCanceled.
|
||||||
func (c *Client) Publish(quit <-chan struct{}, message []byte, topic string) error {
|
func (c *Client) Publish(quit <-chan struct{}, message []byte, topic string) error {
|
||||||
@@ -445,10 +448,9 @@ func (c *Client) PublishRetained(quit <-chan struct{}, message []byte, topic str
|
|||||||
// This delivery method requires a response transmission plus persistence on
|
// This delivery method requires a response transmission plus persistence on
|
||||||
// both client-side and broker-side.
|
// both client-side and broker-side.
|
||||||
//
|
//
|
||||||
// The acknowledge channel is closed uppon receival confirmation by the broker.
|
// The exchange channel is closed uppon receival confirmation by the broker.
|
||||||
// ErrClosed leaves the channel blocked (with no further input). A blocked send
|
// ErrClosed leaves the channel blocked (with no further input).
|
||||||
// from ReadSlices causes the error to be returned instead.
|
func (c *Client) PublishAtLeastOnce(message []byte, topic string) (exchange <-chan error, err error) {
|
||||||
func (c *Client) PublishAtLeastOnce(message []byte, topic string) (ack <-chan error, err error) {
|
|
||||||
buf := bufPool.Get().(*[bufSize]byte)
|
buf := bufPool.Get().(*[bufSize]byte)
|
||||||
defer bufPool.Put(buf)
|
defer bufPool.Put(buf)
|
||||||
packet, err := appendPublishPacket(buf, message, topic, atLeastOnceIDSpace, typePUBLISH<<4|atLeastOnceLevel<<1)
|
packet, err := appendPublishPacket(buf, message, topic, atLeastOnceIDSpace, typePUBLISH<<4|atLeastOnceLevel<<1)
|
||||||
@@ -463,7 +465,7 @@ func (c *Client) PublishAtLeastOnce(message []byte, topic string) (ack <-chan er
|
|||||||
// subscriptions match the topic name. When a new subscription is established,
|
// subscriptions match the topic name. When a new subscription is established,
|
||||||
// the last retained message, if any, on each matching topic name must be sent
|
// the last retained message, if any, on each matching topic name must be sent
|
||||||
// to the subscriber.
|
// to the subscriber.
|
||||||
func (c *Client) PublishAtLeastOnceRetained(message []byte, topic string) (ack <-chan error, err error) {
|
func (c *Client) PublishAtLeastOnceRetained(message []byte, topic string) (exchange <-chan error, err error) {
|
||||||
buf := bufPool.Get().(*[bufSize]byte)
|
buf := bufPool.Get().(*[bufSize]byte)
|
||||||
defer bufPool.Put(buf)
|
defer bufPool.Put(buf)
|
||||||
packet, err := appendPublishPacket(buf, message, topic, atLeastOnceIDSpace, typePUBLISH<<4|atLeastOnceLevel<<1|retainFlag)
|
packet, err := appendPublishPacket(buf, message, topic, atLeastOnceIDSpace, typePUBLISH<<4|atLeastOnceLevel<<1|retainFlag)
|
||||||
@@ -476,7 +478,7 @@ func (c *Client) PublishAtLeastOnceRetained(message []byte, topic string) (ack <
|
|||||||
// PublishExactlyOnce delivers the message with an “exactly once” guarantee.
|
// PublishExactlyOnce delivers the message with an “exactly once” guarantee.
|
||||||
// This delivery method eliminates the duplicate-delivery risk from
|
// This delivery method eliminates the duplicate-delivery risk from
|
||||||
// PublishAtLeastOnce at the expense of an additional network roundtrip.
|
// PublishAtLeastOnce at the expense of an additional network roundtrip.
|
||||||
func (c *Client) PublishExactlyOnce(message []byte, topic string) (ack <-chan error, err error) {
|
func (c *Client) PublishExactlyOnce(message []byte, topic string) (exchange <-chan error, err error) {
|
||||||
buf := bufPool.Get().(*[bufSize]byte)
|
buf := bufPool.Get().(*[bufSize]byte)
|
||||||
defer bufPool.Put(buf)
|
defer bufPool.Put(buf)
|
||||||
packet, err := appendPublishPacket(buf, message, topic, exactlyOnceIDSpace, typePUBLISH<<4|exactlyOnceLevel<<1)
|
packet, err := appendPublishPacket(buf, message, topic, exactlyOnceIDSpace, typePUBLISH<<4|exactlyOnceLevel<<1)
|
||||||
@@ -491,7 +493,7 @@ func (c *Client) PublishExactlyOnce(message []byte, topic string) (ack <-chan er
|
|||||||
// subscriptions match the topic name. When a new subscription is established,
|
// subscriptions match the topic name. When a new subscription is established,
|
||||||
// the last retained message, if any, on each matching topic name must be sent
|
// the last retained message, if any, on each matching topic name must be sent
|
||||||
// to the subscriber.
|
// to the subscriber.
|
||||||
func (c *Client) PublishExactlyOnceRetained(message []byte, topic string) (ack <-chan error, err error) {
|
func (c *Client) PublishExactlyOnceRetained(message []byte, topic string) (exchange <-chan error, err error) {
|
||||||
buf := bufPool.Get().(*[bufSize]byte)
|
buf := bufPool.Get().(*[bufSize]byte)
|
||||||
defer bufPool.Put(buf)
|
defer bufPool.Put(buf)
|
||||||
packet, err := appendPublishPacket(buf, message, topic, exactlyOnceIDSpace, typePUBLISH<<4|exactlyOnceLevel<<1|retainFlag)
|
packet, err := appendPublishPacket(buf, message, topic, exactlyOnceIDSpace, typePUBLISH<<4|exactlyOnceLevel<<1|retainFlag)
|
||||||
@@ -501,8 +503,8 @@ func (c *Client) PublishExactlyOnceRetained(message []byte, topic string) (ack <
|
|||||||
return c.submitPersisted(packet, c.exactlyOnceSem, c.recQ, c.compQ, c.exactlyOnceBlock)
|
return c.submitPersisted(packet, c.exactlyOnceSem, c.recQ, c.compQ, c.exactlyOnceBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) submitPersisted(packet net.Buffers, sem chan uint, ackQ, ackQ2 chan chan<- error, block chan holdup) (ack <-chan error, err error) {
|
func (c *Client) submitPersisted(packet net.Buffers, sem chan uint, ackQ, ackQ2 chan chan<- error, block chan holdup) (exchange <-chan error, err error) {
|
||||||
done := make(chan error, 2)
|
done := make(chan error, 2) // receives at most 1 write error + ErrClosed
|
||||||
select {
|
select {
|
||||||
case counter, ok := <-sem:
|
case counter, ok := <-sem:
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -519,12 +521,11 @@ func (c *Client) submitPersisted(packet net.Buffers, sem chan uint, ackQ, ackQ2
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ackQ <- done // won't block due ErrMax check
|
ackQ <- done // won't block due ErrMax check
|
||||||
|
|
||||||
switch err := c.writeAll(c.Offline(), packet); {
|
switch err := c.writeAll(c.Offline(), packet); {
|
||||||
case err == nil:
|
case err == nil:
|
||||||
sem <- counter + 1
|
sem <- counter + 1
|
||||||
case errors.Is(err, ErrCanceled):
|
case errors.Is(err, ErrCanceled), errors.Is(err, ErrDown):
|
||||||
done <- ErrDown
|
// don't report down
|
||||||
block <- holdup{SinceSeqNo: counter, UntilSeqNo: counter}
|
block <- holdup{SinceSeqNo: counter, UntilSeqNo: counter}
|
||||||
default:
|
default:
|
||||||
done <- err
|
done <- err
|
||||||
@@ -545,7 +546,6 @@ func (c *Client) submitPersisted(packet net.Buffers, sem chan uint, ackQ, ackQ2
|
|||||||
ackQ <- done // won't block due ErrMax check
|
ackQ <- done // won't block due ErrMax check
|
||||||
holdup.UntilSeqNo++
|
holdup.UntilSeqNo++
|
||||||
block <- holdup
|
block <- holdup
|
||||||
done <- ErrDown
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return done, nil
|
return done, nil
|
||||||
@@ -553,14 +553,14 @@ func (c *Client) submitPersisted(packet net.Buffers, sem chan uint, ackQ, ackQ2
|
|||||||
|
|
||||||
func appendPublishPacket(buf *[bufSize]byte, message []byte, topic string, packetID uint, head byte) (net.Buffers, error) {
|
func appendPublishPacket(buf *[bufSize]byte, message []byte, topic string, packetID uint, head byte) (net.Buffers, error) {
|
||||||
if err := stringCheck(topic); err != nil {
|
if err := stringCheck(topic); err != nil {
|
||||||
return nil, fmt.Errorf("mqtt: PUBLISH denied due topic: %w", err)
|
return nil, fmt.Errorf("mqtt: PUBLISH request denied due topic: %w", err)
|
||||||
}
|
}
|
||||||
size := 2 + len(topic) + len(message)
|
size := 2 + len(topic) + len(message)
|
||||||
if packetID != 0 {
|
if packetID != 0 {
|
||||||
size += 2
|
size += 2
|
||||||
}
|
}
|
||||||
if size < 0 || size > packetMax {
|
if size < 0 || size > packetMax {
|
||||||
return nil, fmt.Errorf("mqtt: PUBLISH denied: %w", errPacketMax)
|
return nil, fmt.Errorf("mqtt: PUBLISH request denied: %w", errPacketMax)
|
||||||
}
|
}
|
||||||
|
|
||||||
packet := append(buf[:0], head)
|
packet := append(buf[:0], head)
|
||||||
|
Reference in New Issue
Block a user