Skip to content

Commit f491e09

Browse files
authored
[Improve][Producer] normalize and export the errors (#1143)
* [Improve][Producer] normalize and export the errors * update schema error * update go version to 1.20 to support errors.Join() * use errors.Is() to test an error * use github.com/hashicorp/go-multierror to join errors instead of errors.Join() of Go 1.20 * revert go version to 1.18 * revert go version to 1.18 * update ErrSchema according to the CR sugguestions * update ErrTransaction to a normal error * rename ErrProducerBlocked to ErrProducerBlockedQuotaExceeded * add license header * fix unit test error --------- Co-authored-by: gunli <[email protected]>
1 parent 443072b commit f491e09

File tree

7 files changed

+104
-47
lines changed

7 files changed

+104
-47
lines changed

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ require (
4343
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
4444
github.com/golang/snappy v0.0.1 // indirect
4545
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
46+
github.com/hashicorp/errwrap v1.0.0 // indirect
47+
github.com/hashicorp/go-multierror v1.1.1 // indirect
4648
github.com/inconshreveable/mousetrap v1.0.1 // indirect
4749
github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect
4850
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
160160
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
161161
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
162162
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
163+
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
164+
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
165+
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
166+
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
163167
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
164168
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
165169
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=

pulsar/consumer_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4299,7 +4299,7 @@ func TestConsumerMemoryLimit(t *testing.T) {
42994299
Payload: createTestMessagePayload(1),
43004300
})
43014301
// Producer can't send message
4302-
assert.Equal(t, true, errors.Is(err, errMemoryBufferIsFull))
4302+
assert.Equal(t, true, errors.Is(err, ErrMemoryBufferIsFull))
43034303
}
43044304

43054305
func TestMultiConsumerMemoryLimit(t *testing.T) {

pulsar/error.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"fmt"
2222

2323
proto "github.com/apache/pulsar-client-go/pulsar/internal/pulsar_proto"
24+
"github.com/hashicorp/go-multierror"
2425
)
2526

2627
// Result used to represent pulsar processing is an alias of type int.
@@ -245,3 +246,10 @@ func getErrorFromServerError(serverError *proto.ServerError) error {
245246
return newError(UnknownError, serverError.String())
246247
}
247248
}
249+
250+
// joinErrors can join multiple errors into one error, and the returned error can be tested by errors.Is()
251+
// we use github.com/hashicorp/go-multierror instead of errors.Join() of Go 1.20 so that we can compile pulsar
252+
// go client with go versions that newer than go 1.13
253+
func joinErrors(errs ...error) error {
254+
return multierror.Append(nil, errs...)
255+
}

pulsar/error_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package pulsar
19+
20+
import (
21+
"errors"
22+
"testing"
23+
24+
"github.com/stretchr/testify/assert"
25+
)
26+
27+
func Test_joinErrors(t *testing.T) {
28+
err1 := errors.New("err1")
29+
err2 := errors.New("err2")
30+
err3 := errors.New("err3")
31+
err := joinErrors(ErrInvalidMessage, err1, err2)
32+
assert.True(t, errors.Is(err, ErrInvalidMessage))
33+
assert.True(t, errors.Is(err, err1))
34+
assert.True(t, errors.Is(err, err2))
35+
assert.False(t, errors.Is(err, err3))
36+
}

pulsar/producer_partition.go

Lines changed: 46 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,21 @@ const (
5151
)
5252

5353
var (
54-
errFailAddToBatch = newError(AddToBatchFailed, "message add to batch failed")
55-
errSendTimeout = newError(TimeoutError, "message send timeout")
56-
errSendQueueIsFull = newError(ProducerQueueIsFull, "producer send queue is full")
57-
errContextExpired = newError(TimeoutError, "message send context expired")
58-
errMessageTooLarge = newError(MessageTooBig, "message size exceeds MaxMessageSize")
59-
errMetaTooLarge = newError(InvalidMessage, "message metadata size exceeds MaxMessageSize")
60-
errProducerClosed = newError(ProducerClosed, "producer already been closed")
61-
errMemoryBufferIsFull = newError(ClientMemoryBufferIsFull, "client memory buffer is full")
54+
ErrFailAddToBatch = newError(AddToBatchFailed, "message add to batch failed")
55+
ErrSendTimeout = newError(TimeoutError, "message send timeout")
56+
ErrSendQueueIsFull = newError(ProducerQueueIsFull, "producer send queue is full")
57+
ErrContextExpired = newError(TimeoutError, "message send context expired")
58+
ErrMessageTooLarge = newError(MessageTooBig, "message size exceeds MaxMessageSize")
59+
ErrMetaTooLarge = newError(InvalidMessage, "message metadata size exceeds MaxMessageSize")
60+
ErrProducerClosed = newError(ProducerClosed, "producer already been closed")
61+
ErrMemoryBufferIsFull = newError(ClientMemoryBufferIsFull, "client memory buffer is full")
62+
ErrSchema = newError(SchemaFailure, "schema error")
63+
ErrTransaction = errors.New("transaction error")
64+
ErrInvalidMessage = newError(InvalidMessage, "invalid message")
65+
ErrTopicNotfound = newError(TopicNotFound, "topic not found")
66+
ErrTopicTerminated = newError(TopicTerminated, "topic terminated")
67+
ErrProducerBlockedQuotaExceeded = newError(ProducerBlockedQuotaExceededException, "producer blocked")
68+
ErrProducerFenced = newError(ProducerFenced, "producer fenced")
6269

6370
buffersPool sync.Pool
6471
sendRequestPool *sync.Pool
@@ -449,25 +456,25 @@ func (p *partitionProducer) reconnectToBroker() {
449456
if strings.Contains(errMsg, errMsgTopicNotFound) {
450457
// when topic is deleted, we should give up reconnection.
451458
p.log.Warn("Topic not found, stop reconnecting, close the producer")
452-
p.doClose(newError(TopicNotFound, err.Error()))
459+
p.doClose(joinErrors(ErrTopicNotfound, err))
453460
break
454461
}
455462

456463
if strings.Contains(errMsg, errMsgTopicTerminated) {
457464
p.log.Warn("Topic was terminated, failing pending messages, stop reconnecting, close the producer")
458-
p.doClose(newError(TopicTerminated, err.Error()))
465+
p.doClose(joinErrors(ErrTopicTerminated, err))
459466
break
460467
}
461468

462469
if strings.Contains(errMsg, errMsgProducerBlockedQuotaExceededException) {
463470
p.log.Warn("Producer was blocked by quota exceed exception, failing pending messages, stop reconnecting")
464-
p.failPendingMessages(newError(ProducerBlockedQuotaExceededException, err.Error()))
471+
p.failPendingMessages(joinErrors(ErrProducerBlockedQuotaExceeded, err))
465472
break
466473
}
467474

468475
if strings.Contains(errMsg, errMsgProducerFenced) {
469476
p.log.Warn("Producer was fenced, failing pending messages, stop reconnecting")
470-
p.doClose(newError(ProducerFenced, err.Error()))
477+
p.doClose(joinErrors(ErrProducerFenced, err))
471478
break
472479
}
473480

@@ -547,7 +554,7 @@ func (p *partitionProducer) internalSend(sr *sendRequest) {
547554
p.log.WithField("size", sr.uncompressedSize).
548555
WithField("properties", sr.msg.Properties).
549556
Error("unable to add message to batch")
550-
sr.done(nil, errFailAddToBatch)
557+
sr.done(nil, ErrFailAddToBatch)
551558
return
552559
}
553560
}
@@ -802,7 +809,7 @@ func (p *partitionProducer) internalFlushCurrentBatch() {
802809
}
803810

804811
if errors.Is(err, internal.ErrExceedMaxMessageSize) {
805-
p.log.WithError(errMessageTooLarge).Errorf("internal err: %s", err)
812+
p.log.WithError(ErrMessageTooLarge).Errorf("internal err: %s", err)
806813
}
807814

808815
return
@@ -893,11 +900,11 @@ func (p *partitionProducer) failTimeoutMessages() {
893900

894901
for _, i := range pi.sendRequests {
895902
sr := i.(*sendRequest)
896-
sr.done(nil, errSendTimeout)
903+
sr.done(nil, ErrSendTimeout)
897904
}
898905

899906
// flag the sending has completed with error, flush make no effect
900-
pi.done(errSendTimeout)
907+
pi.done(ErrSendTimeout)
901908
pi.Unlock()
902909

903910
// finally reached the last view item, current iteration ends
@@ -926,7 +933,7 @@ func (p *partitionProducer) internalFlushCurrentBatches() {
926933
}
927934

928935
if errors.Is(errs[i], internal.ErrExceedMaxMessageSize) {
929-
p.log.WithError(errMessageTooLarge).Errorf("internal err: %s", errs[i])
936+
p.log.WithError(ErrMessageTooLarge).Errorf("internal err: %s", errs[i])
930937
return
931938
}
932939

@@ -1019,18 +1026,18 @@ func (p *partitionProducer) SendAsync(ctx context.Context, msg *ProducerMessage,
10191026

10201027
func (p *partitionProducer) validateMsg(msg *ProducerMessage) error {
10211028
if msg == nil {
1022-
return newError(InvalidMessage, "Message is nil")
1029+
return joinErrors(ErrInvalidMessage, fmt.Errorf("message is nil"))
10231030
}
10241031

10251032
if msg.Value != nil && msg.Payload != nil {
1026-
return newError(InvalidMessage, "Can not set Value and Payload both")
1033+
return joinErrors(ErrInvalidMessage, fmt.Errorf("can not set Value and Payload both"))
10271034
}
10281035

10291036
if p.options.DisableMultiSchema {
10301037
if msg.Schema != nil && p.options.Schema != nil &&
10311038
msg.Schema.GetSchemaInfo().hash() != p.options.Schema.GetSchemaInfo().hash() {
10321039
p.log.Errorf("The producer %s of the topic %s is disabled the `MultiSchema`", p.producerName, p.topic)
1033-
return fmt.Errorf("msg schema can not match with producer schema")
1040+
return joinErrors(ErrSchema, fmt.Errorf("msg schema can not match with producer schema"))
10341041
}
10351042
}
10361043

@@ -1046,15 +1053,16 @@ func (p *partitionProducer) prepareTransaction(sr *sendRequest) error {
10461053
if txn.state != TxnOpen {
10471054
p.log.WithField("state", txn.state).Error("Failed to send message" +
10481055
" by a non-open transaction.")
1049-
return newError(InvalidStatus, "Failed to send message by a non-open transaction.")
1056+
return joinErrors(ErrTransaction,
1057+
fmt.Errorf("failed to send message by a non-open transaction"))
10501058
}
10511059

10521060
if err := txn.registerProducerTopic(p.topic); err != nil {
1053-
return err
1061+
return joinErrors(ErrTransaction, err)
10541062
}
10551063

10561064
if err := txn.registerSendOrAckOp(); err != nil {
1057-
return err
1065+
return joinErrors(ErrTransaction, err)
10581066
}
10591067

10601068
sr.transaction = txn
@@ -1080,7 +1088,7 @@ func (p *partitionProducer) updateSchema(sr *sendRequest) error {
10801088
if schemaVersion == nil {
10811089
schemaVersion, err = p.getOrCreateSchema(schema.GetSchemaInfo())
10821090
if err != nil {
1083-
return fmt.Errorf("get schema version fail, err: %w", err)
1091+
return joinErrors(ErrSchema, fmt.Errorf("get schema version fail, err: %w", err))
10841092
}
10851093
p.schemaCache.Put(schema.GetSchemaInfo(), schemaVersion)
10861094
}
@@ -1097,15 +1105,15 @@ func (p *partitionProducer) updateUncompressedPayload(sr *sendRequest) error {
10971105
if sr.msg.Value != nil {
10981106
if sr.schema == nil {
10991107
p.log.Errorf("Schema encode message failed %s", sr.msg.Value)
1100-
return newError(SchemaFailure, "set schema value without setting schema")
1108+
return joinErrors(ErrSchema, fmt.Errorf("set schema value without setting schema"))
11011109
}
11021110

11031111
// payload and schema are mutually exclusive
11041112
// try to get payload from schema value only if payload is not set
11051113
schemaPayload, err := sr.schema.Encode(sr.msg.Value)
11061114
if err != nil {
11071115
p.log.WithError(err).Errorf("Schema encode message failed %s", sr.msg.Value)
1108-
return newError(SchemaFailure, err.Error())
1116+
return joinErrors(ErrSchema, err)
11091117
}
11101118

11111119
sr.uncompressedPayload = schemaPayload
@@ -1160,11 +1168,11 @@ func (p *partitionProducer) updateChunkInfo(sr *sendRequest) error {
11601168

11611169
// if msg is too large and chunking is disabled
11621170
if checkSize > int64(sr.maxMessageSize) && !p.options.EnableChunking {
1163-
p.log.WithError(errMessageTooLarge).
1171+
p.log.WithError(ErrMessageTooLarge).
11641172
WithField("size", checkSize).
11651173
WithField("properties", sr.msg.Properties).
11661174
Errorf("MaxMessageSize %d", sr.maxMessageSize)
1167-
return errMessageTooLarge
1175+
return ErrMessageTooLarge
11681176
}
11691177

11701178
if sr.sendAsBatch || !p.options.EnableChunking {
@@ -1173,11 +1181,11 @@ func (p *partitionProducer) updateChunkInfo(sr *sendRequest) error {
11731181
} else {
11741182
sr.payloadChunkSize = int(sr.maxMessageSize) - proto.Size(sr.mm)
11751183
if sr.payloadChunkSize <= 0 {
1176-
p.log.WithError(errMetaTooLarge).
1184+
p.log.WithError(ErrMetaTooLarge).
11771185
WithField("metadata size", proto.Size(sr.mm)).
11781186
WithField("properties", sr.msg.Properties).
11791187
Errorf("MaxMessageSize %d", int(p._getConn().GetMaxMessageSize()))
1180-
return errMetaTooLarge
1188+
return ErrMetaTooLarge
11811189
}
11821190
// set ChunkMaxMessageSize
11831191
if p.options.ChunkMaxMessageSize != 0 {
@@ -1220,7 +1228,7 @@ func (p *partitionProducer) internalSendAsync(
12201228
}
12211229

12221230
if p.getProducerState() != producerReady {
1223-
sr.done(nil, errProducerClosed)
1231+
sr.done(nil, ErrProducerClosed)
12241232
return
12251233
}
12261234

@@ -1333,7 +1341,7 @@ func (p *partitionProducer) ReceivedSendReceipt(response *pb.CommandSendReceipt)
13331341
func (p *partitionProducer) internalClose(req *closeProducer) {
13341342
defer close(req.doneCh)
13351343

1336-
p.doClose(errProducerClosed)
1344+
p.doClose(ErrProducerClosed)
13371345
}
13381346

13391347
func (p *partitionProducer) doClose(reason error) {
@@ -1508,11 +1516,11 @@ func (sr *sendRequest) done(msgID MessageID, err error) {
15081516
WithField("properties", sr.msg.Properties)
15091517
}
15101518

1511-
if errors.Is(err, errSendTimeout) {
1519+
if errors.Is(err, ErrSendTimeout) {
15121520
sr.producer.metrics.PublishErrorsTimeout.Inc()
15131521
}
15141522

1515-
if errors.Is(err, errMessageTooLarge) {
1523+
if errors.Is(err, ErrMessageTooLarge) {
15161524
sr.producer.metrics.PublishErrorsMsgTooLarge.Inc()
15171525
}
15181526

@@ -1554,7 +1562,7 @@ func (p *partitionProducer) reserveSemaphore(sr *sendRequest) error {
15541562
for i := 0; i < sr.totalChunks; i++ {
15551563
if p.blockIfQueueFull() {
15561564
if !p.publishSemaphore.Acquire(sr.ctx) {
1557-
return errContextExpired
1565+
return ErrContextExpired
15581566
}
15591567

15601568
// update sr.semaphore and sr.reservedSemaphore here so that we can release semaphore in the case
@@ -1564,7 +1572,7 @@ func (p *partitionProducer) reserveSemaphore(sr *sendRequest) error {
15641572
p.metrics.MessagesPending.Inc()
15651573
} else {
15661574
if !p.publishSemaphore.TryAcquire() {
1567-
return errSendQueueIsFull
1575+
return ErrSendQueueIsFull
15681576
}
15691577

15701578
// update sr.semaphore and sr.reservedSemaphore here so that we can release semaphore in the case
@@ -1586,11 +1594,11 @@ func (p *partitionProducer) reserveMem(sr *sendRequest) error {
15861594

15871595
if p.blockIfQueueFull() {
15881596
if !p.client.memLimit.ReserveMemory(sr.ctx, requiredMem) {
1589-
return errContextExpired
1597+
return ErrContextExpired
15901598
}
15911599
} else {
15921600
if !p.client.memLimit.TryReserveMemory(requiredMem) {
1593-
return errMemoryBufferIsFull
1601+
return ErrMemoryBufferIsFull
15941602
}
15951603
}
15961604

0 commit comments

Comments
 (0)