Skip to content

Commit 7442b12

Browse files
author
Luis Sanchez
committed
Do not block on Broadcast responses
FAB-839 Added per-connection buffered channel of size QueueSize for responses. Added unit test TestBroadcastResponseQueueOverflow. Change-Id: I317d127f74dcc8115201f8c127e83230d9d13a58 Signed-off-by: Luis Sanchez <[email protected]>
1 parent 82e72f4 commit 7442b12

6 files changed

+91
-13
lines changed

orderer/kafka/broadcast.go

+36-7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"sync"
2222
"time"
2323

24+
"golang.org/x/net/context"
25+
2426
"github.com/hyperledger/fabric/orderer/config"
2527
cb "github.com/hyperledger/fabric/protos/common"
2628
ab "github.com/hyperledger/fabric/protos/orderer"
@@ -112,7 +114,7 @@ func (b *broadcasterImpl) cutBlock(period time.Duration, maxSize uint) {
112114
case msg := <-b.batchChan:
113115
data, err := proto.Marshal(msg)
114116
if err != nil {
115-
logger.Fatalf("Error marshaling what should be a valid proto message: %s", err)
117+
panic(fmt.Errorf("Error marshaling what should be a valid proto message: %s", err))
116118
}
117119
b.messages = append(b.messages, data)
118120
if len(b.messages) >= int(maxSize) {
@@ -136,7 +138,9 @@ func (b *broadcasterImpl) cutBlock(period time.Duration, maxSize uint) {
136138
}
137139

138140
func (b *broadcasterImpl) recvRequests(stream ab.AtomicBroadcast_BroadcastServer) error {
139-
reply := new(ab.BroadcastResponse)
141+
context, cancel := context.WithCancel(context.Background())
142+
defer cancel()
143+
bsr := newBroadcastSessionResponder(context, stream, b.config.General.QueueSize)
140144
for {
141145
msg, err := stream.Recv()
142146
if err != nil {
@@ -145,12 +149,37 @@ func (b *broadcasterImpl) recvRequests(stream ab.AtomicBroadcast_BroadcastServer
145149
}
146150

147151
b.batchChan <- msg
148-
reply.Status = cb.Status_SUCCESS // TODO This shouldn't always be a success
152+
bsr.reply(cb.Status_SUCCESS) // TODO This shouldn't always be a success
149153

150-
if err := stream.Send(reply); err != nil {
151-
logger.Info("Cannot send broadcast reply to client")
152-
return err
154+
}
155+
}
156+
157+
type broadcastSessionResponder struct {
158+
queue chan *ab.BroadcastResponse
159+
}
160+
161+
func newBroadcastSessionResponder(context context.Context, stream ab.AtomicBroadcast_BroadcastServer, queueSize uint) *broadcastSessionResponder {
162+
bsr := &broadcastSessionResponder{
163+
queue: make(chan *ab.BroadcastResponse, queueSize),
164+
}
165+
go bsr.sendReplies(context, stream)
166+
return bsr
167+
}
168+
169+
func (bsr *broadcastSessionResponder) reply(status cb.Status) {
170+
bsr.queue <- &ab.BroadcastResponse{Status: status}
171+
}
172+
173+
func (bsr *broadcastSessionResponder) sendReplies(context context.Context, stream ab.AtomicBroadcast_BroadcastServer) {
174+
for {
175+
select {
176+
case reply := <-bsr.queue:
177+
if err := stream.Send(reply); err != nil {
178+
logger.Info("Cannot send broadcast reply to client")
179+
}
180+
logger.Debugf("Sent broadcast reply %v to client", reply.Status.String())
181+
case <-context.Done():
182+
return
153183
}
154-
logger.Debugf("Sent broadcast reply %v to client", reply.Status.String())
155184
}
156185
}

orderer/kafka/broadcast_mock_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func mockNewBroadcaster(t *testing.T, conf *config.TopLevel, seek int64, disk ch
2727
mb := &broadcasterImpl{
2828
producer: mockNewProducer(t, conf, seek, disk),
2929
config: conf,
30-
batchChan: make(chan *cb.Envelope, conf.General.BatchSize),
30+
batchChan: make(chan *cb.Envelope, batchChanSize),
3131
messages: [][]byte{[]byte("checkpoint")},
3232
nextNumber: uint64(seek),
3333
}

orderer/kafka/broadcast_test.go

+47
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,53 @@ func TestBroadcastBatch(t *testing.T) {
135135
}
136136
}
137137

138+
// If the capacity of the response queue is less than the batch size,
139+
// then if the response queue overflows, the order should not be able
140+
// to send back a block to the client. (Sending replies and adding
141+
// messages to the about-to-be-sent block happens on the same routine.)
142+
func TestBroadcastResponseQueueOverflow(t *testing.T) {
143+
144+
// Make sure that the response queue is less than the batch size
145+
originalQueueSize := testConf.General.QueueSize
146+
defer func() { testConf.General.QueueSize = originalQueueSize }()
147+
testConf.General.QueueSize = testConf.General.BatchSize - 1
148+
149+
disk := make(chan []byte)
150+
151+
mb := mockNewBroadcaster(t, testConf, oldestOffset, disk)
152+
defer testClose(t, mb)
153+
154+
mbs := newMockBroadcastStream(t)
155+
go func() {
156+
if err := mb.Broadcast(mbs); err != nil {
157+
t.Fatal("Broadcast error:", err)
158+
}
159+
}()
160+
161+
<-disk // We tested the checkpoint block in a previous test, so we can ignore it now
162+
163+
// Force the response queue to overflow by blocking the broadcast stream's Send() method
164+
mbs.closed = true
165+
defer func() { mbs.closed = false }()
166+
167+
// Pump a batch's worth of messages into the system
168+
go func() {
169+
for i := 0; i < int(testConf.General.BatchSize); i++ {
170+
mbs.incoming <- &cb.Envelope{Payload: []byte("message " + strconv.Itoa(i))}
171+
}
172+
}()
173+
174+
loop:
175+
for {
176+
select {
177+
case <-mbs.outgoing:
178+
t.Fatal("Client shouldn't have received anything from the orderer")
179+
case <-time.After(testConf.General.BatchTimeout + timePadding):
180+
break loop // This is the success path
181+
}
182+
}
183+
}
184+
138185
func TestBroadcastIncompleteBatch(t *testing.T) {
139186
if testConf.General.BatchSize <= 1 {
140187
t.Skip("Skipping test as it requires a batchsize > 1")

orderer/kafka/config_test.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ import (
2424
)
2525

2626
var (
27-
brokerID = int32(0)
28-
oldestOffset = int64(100) // The oldest block available on the broker
29-
newestOffset = int64(1100) // The offset that will be assigned to the next block
30-
middleOffset = (oldestOffset + newestOffset - 1) / 2 // Just an offset in the middle
27+
brokerID = int32(0)
28+
oldestOffset = int64(100) // The oldest block available on the broker
29+
newestOffset = int64(1100) // The offset that will be assigned to the next block
30+
middleOffset = (oldestOffset + newestOffset - 1) / 2 // Just an offset in the middle
31+
batchChanSize = 1000 // Size of batch channel (eventually sync with FAB-821)
3132

3233
// Amount of time to wait for block processing when doing time-based tests
3334
// We generally want this value to be as small as possible so as to make tests execute faster

orderer/kafka/orderer_mock_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ func (mbs *mockBroadcastStream) Recv() (*cb.Envelope, error) {
5353
}
5454

5555
func (mbs *mockBroadcastStream) Send(reply *ab.BroadcastResponse) error {
56+
for mbs.closed {
57+
}
5658
if !mbs.closed {
5759
mbs.outgoing <- reply
5860
}

orderer/orderer.yaml

-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ General:
2525
BatchSize: 10
2626

2727
# Queue Size: The maximum number of messages to allow pending from a gRPC client
28-
# When Kafka is chosen as the OrdererType, this option is ignored.
2928
QueueSize: 10
3029

3130
# Max Window Size: The maximum number of messages to for the orderer Deliver

0 commit comments

Comments
 (0)