Skip to content

Commit d2f2029

Browse files
committed
[FAB-12816] cluster.RPC mapping per destination.
The egress wrapper for the cluster communication caches the last Submit stream and only reacquires it if a Send() or Recv() was attempted and failed. This is problematic because a leader may change while there is no call to Send() or Recv() in a follower node, and then the stream will be mapped to a node that is no longer the leader. This change set makes the mapping be per destination and not global, by adding a map that maps destinations to streams. Change-Id: I028bde6fb2248cb8a56ad622be4e73827834caf4 Signed-off-by: yacovm <yacovm@il.ibm.com>
1 parent c1ff9e6 commit d2f2029

File tree

3 files changed

+79
-14
lines changed

3 files changed

+79
-14
lines changed

orderer/common/cluster/rpc.go

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package cluster
88

99
import (
1010
"context"
11+
"sync"
1112

1213
"github.com/hyperledger/fabric/protos/orderer"
1314
"github.com/pkg/errors"
@@ -36,9 +37,10 @@ type Client interface {
3637

3738
// RPC performs remote procedure calls to remote cluster nodes.
3839
type RPC struct {
39-
stream orderer.Cluster_SubmitClient
40-
Channel string
41-
Comm Communicator
40+
Channel string
41+
Comm Communicator
42+
lock sync.RWMutex
43+
DestinationToStream map[uint64]orderer.Cluster_SubmitClient
4244
}
4345

4446
// Step sends a StepRequest to the given destination node and returns the response
@@ -58,7 +60,7 @@ func (s *RPC) SendSubmit(destination uint64, request *orderer.SubmitRequest) err
5860
}
5961
err = stream.Send(request)
6062
if err != nil {
61-
s.stream = nil
63+
s.unMapStream(destination)
6264
}
6365
return err
6466
}
@@ -71,24 +73,43 @@ func (s *RPC) ReceiveSubmitResponse(destination uint64) (*orderer.SubmitResponse
7173
}
7274
msg, err := stream.Recv()
7375
if err != nil {
74-
s.stream = nil
76+
s.unMapStream(destination)
7577
}
7678
return msg, err
7779
}
7880

7981
// getProposeStream obtains a Submit stream for the given destination node
8082
func (s *RPC) getProposeStream(destination uint64) (orderer.Cluster_SubmitClient, error) {
81-
if s.stream != nil {
82-
return s.stream, nil
83+
stream := s.getStream(destination)
84+
if stream != nil {
85+
return stream, nil
8386
}
8487
stub, err := s.Comm.Remote(s.Channel, destination)
8588
if err != nil {
8689
return nil, errors.WithStack(err)
8790
}
88-
stream, err := stub.SubmitStream()
91+
stream, err = stub.SubmitStream()
8992
if err != nil {
9093
return nil, errors.WithStack(err)
9194
}
92-
s.stream = stream
95+
s.mapStream(destination, stream)
9396
return stream, nil
9497
}
98+
99+
func (s *RPC) getStream(destination uint64) orderer.Cluster_SubmitClient {
100+
s.lock.RLock()
101+
defer s.lock.RUnlock()
102+
return s.DestinationToStream[destination]
103+
}
104+
105+
func (s *RPC) mapStream(destination uint64, stream orderer.Cluster_SubmitClient) {
106+
s.lock.Lock()
107+
defer s.lock.Unlock()
108+
s.DestinationToStream[destination] = stream
109+
}
110+
111+
func (s *RPC) unMapStream(destination uint64) {
112+
s.lock.Lock()
113+
defer s.lock.Unlock()
114+
delete(s.DestinationToStream, destination)
115+
}

orderer/common/cluster/rpc_test.go

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,9 @@ func TestRPCStep(t *testing.T) {
5656
}, testcase.remoteErr)
5757

5858
rpc := &cluster.RPC{
59-
Channel: "mychannel",
60-
Comm: comm,
59+
DestinationToStream: make(map[uint64]orderer.Cluster_SubmitClient),
60+
Channel: "mychannel",
61+
Comm: comm,
6162
}
6263

6364
response, err := rpc.Step(1, &orderer.StepRequest{
@@ -74,6 +75,44 @@ func TestRPCStep(t *testing.T) {
7475
}
7576
}
7677

78+
func TestRPCChangeDestination(t *testing.T) {
79+
t.Parallel()
80+
// We send a Submit() to 2 different nodes - 1 and 2.
81+
// The first invocation of Submit() establishes a stream with node 1
82+
// and the second establishes a stream with node 2.
83+
// We define a mock behavior for only a single invocation of Send() on each
84+
// of the streams (to node 1 and to node 2), therefore we test that invocation
85+
// of rpc.SendSubmit to node 2 doesn't send the message to node 1.
86+
comm := &mocks.Communicator{}
87+
88+
client1 := &mocks.ClusterClient{}
89+
client2 := &mocks.ClusterClient{}
90+
91+
comm.On("Remote", "mychannel", uint64(1)).Return(&cluster.RemoteContext{Client: client1}, nil)
92+
comm.On("Remote", "mychannel", uint64(2)).Return(&cluster.RemoteContext{Client: client2}, nil)
93+
94+
streamToNode1 := &mocks.SubmitClient{}
95+
streamToNode2 := &mocks.SubmitClient{}
96+
97+
client1.On("Submit", mock.Anything).Return(streamToNode1, nil).Once()
98+
client2.On("Submit", mock.Anything).Return(streamToNode2, nil).Once()
99+
100+
rpc := &cluster.RPC{
101+
DestinationToStream: make(map[uint64]orderer.Cluster_SubmitClient),
102+
Channel: "mychannel",
103+
Comm: comm,
104+
}
105+
106+
streamToNode1.On("Send", mock.Anything).Return(nil).Once()
107+
streamToNode2.On("Send", mock.Anything).Return(nil).Once()
108+
109+
rpc.SendSubmit(1, &orderer.SubmitRequest{Channel: "mychannel"})
110+
rpc.SendSubmit(2, &orderer.SubmitRequest{Channel: "mychannel"})
111+
112+
streamToNode1.AssertNumberOfCalls(t, "Send", 1)
113+
streamToNode2.AssertNumberOfCalls(t, "Send", 1)
114+
}
115+
77116
func TestRPCSubmitSend(t *testing.T) {
78117
t.Parallel()
79118
submitRequest := &orderer.SubmitRequest{Channel: "mychannel"}
@@ -149,8 +188,9 @@ func TestRPCSubmitSend(t *testing.T) {
149188
}, testCase.remoteError)
150189

151190
rpc := &cluster.RPC{
152-
Channel: "mychannel",
153-
Comm: comm,
191+
DestinationToStream: make(map[uint64]orderer.Cluster_SubmitClient),
192+
Channel: "mychannel",
193+
Comm: comm,
154194
}
155195

156196
var msg *orderer.SubmitResponse

orderer/consensus/etcdraft/consenter.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,11 @@ func (c *Consenter) HandleChain(support consensus.ConsenterSupport, metadata *co
147147
SnapDir: path.Join(c.EtcdRaftConfig.SnapDir, support.ChainID()),
148148
}
149149

150-
rpc := &cluster.RPC{Channel: support.ChainID(), Comm: c.Communication}
150+
rpc := &cluster.RPC{
151+
Channel: support.ChainID(),
152+
Comm: c.Communication,
153+
DestinationToStream: make(map[uint64]orderer.Cluster_SubmitClient),
154+
}
151155
return NewChain(support, opts, c.Communication, rpc, bp, nil)
152156
}
153157

0 commit comments

Comments
 (0)