Skip to content
This repository was archived by the owner on Apr 25, 2025. It is now read-only.

Commit 47fffbe

Browse files
committed
[FAB-3217] SDK Go - Deadlock in Event Hub
Change-Id: I044d136c69a612e0d6c1189a09222fc372434502 Signed-off-by: Bob Stasyszyn <bob.stasyszyn@securekey.com>
1 parent e12e9c5 commit 47fffbe

File tree

3 files changed

+461
-16
lines changed

3 files changed

+461
-16
lines changed

fabric-client/events/eventhub.go

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@ import (
2525
"regexp"
2626
"sync"
2727

28+
"time"
29+
2830
"github.com/golang/protobuf/proto"
2931
consumer "github.com/hyperledger/fabric-sdk-go/fabric-client/events/consumer"
32+
cnsmr "github.com/hyperledger/fabric/events/consumer"
33+
3034
"github.com/hyperledger/fabric/core/ledger/util"
3135
common "github.com/hyperledger/fabric/protos/common"
3236
pb "github.com/hyperledger/fabric/protos/peer"
@@ -53,6 +57,11 @@ type EventHubExt interface {
5357
AddChaincodeInterest(ChaincodeID string, EventName string)
5458
}
5559

60+
// eventClientFactory creates an EventsClient instance
61+
type eventClientFactory interface {
62+
newEventsClient(peerAddress string, certificate string, serverHostOverride string, regTimeout time.Duration, adapter cnsmr.EventAdapter) (consumer.EventsClient, error)
63+
}
64+
5665
type eventHub struct {
5766
// Protects chaincodeRegistrants, blockRegistrants and txRegistrants
5867
mtx sync.RWMutex
@@ -74,6 +83,8 @@ type eventHub struct {
7483
connected bool
7584
// List of events client is interested in
7685
interestedEvents []*pb.Interest
86+
// Factory that creates EventsClient
87+
eventsClientFactory eventClientFactory
7788
}
7889

7990
// ChaincodeEvent contains the current event data for the event handler
@@ -98,13 +109,26 @@ type ChainCodeCBE struct {
98109
CallbackFunc func(*ChaincodeEvent)
99110
}
100111

112+
// consumerClientFactory is the default implementation oif the eventClientFactory
113+
type consumerClientFactory struct{}
114+
115+
func (ccf *consumerClientFactory) newEventsClient(peerAddress string, certificate string, serverHostOverride string, regTimeout time.Duration, adapter cnsmr.EventAdapter) (consumer.EventsClient, error) {
116+
return consumer.NewEventsClient(peerAddress, certificate, serverHostOverride, regTimeout, adapter)
117+
}
118+
101119
// NewEventHub ...
102120
func NewEventHub() EventHub {
103121
chaincodeRegistrants := make(map[string][]*ChainCodeCBE)
104122
blockRegistrants := make([]func(*common.Block), 0)
105123
txRegistrants := make(map[string]func(string, error))
106124

107-
eventHub := &eventHub{chaincodeRegistrants: chaincodeRegistrants, blockRegistrants: blockRegistrants, txRegistrants: txRegistrants, interestedEvents: nil}
125+
eventHub := &eventHub{
126+
chaincodeRegistrants: chaincodeRegistrants,
127+
blockRegistrants: blockRegistrants,
128+
txRegistrants: txRegistrants,
129+
interestedEvents: nil,
130+
eventsClientFactory: &consumerClientFactory{},
131+
}
108132

109133
// default to listening for block events
110134
eventHub.SetInterests(true)
@@ -177,7 +201,7 @@ func (eventHub *eventHub) Connect() error {
177201
eventHub.blockRegistrants = make([]func(*common.Block), 0)
178202
eventHub.blockRegistrants = append(eventHub.blockRegistrants, eventHub.txCallback)
179203

180-
eventsClient, _ := consumer.NewEventsClient(eventHub.peerAddr, eventHub.peerTLSCertificate, eventHub.peerTLSServerHostOverride, 5, eventHub)
204+
eventsClient, _ := eventHub.eventsClientFactory.newEventsClient(eventHub.peerAddr, eventHub.peerTLSCertificate, eventHub.peerTLSServerHostOverride, 5, eventHub)
181205
if err := eventsClient.Start(); err != nil {
182206
eventsClient.Stop()
183207
return fmt.Errorf("Error from eventsClient.Start (%s)", err.Error())
@@ -195,14 +219,11 @@ func (eventHub *eventHub) GetInterestedEvents() ([]*pb.Interest, error) {
195219

196220
//Recv implements consumer.EventAdapter interface for receiving events
197221
func (eventHub *eventHub) Recv(msg *pb.Event) (bool, error) {
198-
eventHub.mtx.RLock()
199-
defer eventHub.mtx.RUnlock()
200-
201222
switch msg.Event.(type) {
202223
case *pb.Event_Block:
203224
blockEvent := msg.Event.(*pb.Event_Block)
204225
logger.Debugf("Recv blockEvent:%v\n", blockEvent)
205-
for _, v := range eventHub.blockRegistrants {
226+
for _, v := range eventHub.getBlockRegistrants() {
206227
v(blockEvent.Block)
207228
}
208229

@@ -282,15 +303,18 @@ func (eventHub *eventHub) UnregisterChaincodeEvent(cbe *ChainCodeCBE) {
282303
logger.Debugf("No event registration for ccid %s \n", cbe.CCID)
283304
return
284305
}
306+
285307
for i, v := range cbeArray {
286-
if v.EventNameFilter == cbe.EventNameFilter {
287-
cbeArray = append(cbeArray[:i], cbeArray[i+1:]...)
308+
if v == cbe {
309+
newCbeArray := append(cbeArray[:i], cbeArray[i+1:]...)
310+
if len(newCbeArray) <= 0 {
311+
delete(eventHub.chaincodeRegistrants, cbe.CCID)
312+
} else {
313+
eventHub.chaincodeRegistrants[cbe.CCID] = newCbeArray
314+
}
315+
break
288316
}
289317
}
290-
if len(cbeArray) <= 0 {
291-
delete(eventHub.chaincodeRegistrants, cbe.CCID)
292-
}
293-
294318
}
295319

296320
// RegisterTxEvent ...
@@ -330,39 +354,77 @@ func (eventHub *eventHub) UnregisterTxEvent(txID string) {
330354
func (eventHub *eventHub) txCallback(block *common.Block) {
331355
logger.Debugf("txCallback block=%v\n", block)
332356

333-
eventHub.mtx.RLock()
334-
defer eventHub.mtx.RUnlock()
335357
txFilter := util.TxValidationFlags(block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER])
336358
for i, v := range block.Data.Data {
337359
if env, err := utils.GetEnvelopeFromBlock(v); err != nil {
360+
logger.Errorf("error extracting Envelope from block: %v\n", err)
338361
return
339362
} else if env != nil {
340363
// get the payload from the envelope
341364
payload, err := utils.GetPayload(env)
342365
if err != nil {
366+
logger.Errorf("error extracting Payload from envelope: %v\n", err)
343367
return
344368
}
345369

346370
channelHeaderBytes := payload.Header.ChannelHeader
347371
channelHeader := &common.ChannelHeader{}
348372
err = proto.Unmarshal(channelHeaderBytes, channelHeader)
349373
if err != nil {
374+
logger.Errorf("error extracting ChannelHeader from payload: %v\n", err)
350375
return
351376
}
352377

353-
callback := eventHub.txRegistrants[channelHeader.TxId]
378+
callback := eventHub.getTXRegistrant(channelHeader.TxId)
354379
if callback != nil {
355380
if txFilter.IsInvalid(i) {
356381
callback(channelHeader.TxId, fmt.Errorf("Received invalid transaction from channel %s\n", channelHeader.ChannelId))
357382

358383
} else {
359384
callback(channelHeader.TxId, nil)
360385
}
386+
} else {
387+
logger.Debugf("No callback registered for TxID: %s\n", channelHeader.TxId)
361388
}
362389
}
363390
}
364391
}
365392

393+
func (eventHub *eventHub) getBlockRegistrants() []func(*common.Block) {
394+
eventHub.mtx.RLock()
395+
defer eventHub.mtx.RUnlock()
396+
397+
// Return a clone of the array to avoid race conditions
398+
clone := make([]func(*common.Block), len(eventHub.blockRegistrants))
399+
for i, registrant := range eventHub.blockRegistrants {
400+
clone[i] = registrant
401+
}
402+
return clone
403+
}
404+
405+
func (eventHub *eventHub) getChaincodeRegistrants(chaincodeID string) []*ChainCodeCBE {
406+
eventHub.mtx.RLock()
407+
defer eventHub.mtx.RUnlock()
408+
409+
registrants, ok := eventHub.chaincodeRegistrants[chaincodeID]
410+
if !ok {
411+
return nil
412+
}
413+
414+
// Return a clone of the array to avoid race conditions
415+
clone := make([]*ChainCodeCBE, len(registrants))
416+
for i, registrants := range registrants {
417+
clone[i] = registrants
418+
}
419+
return clone
420+
}
421+
422+
func (eventHub *eventHub) getTXRegistrant(txID string) func(string, error) {
423+
eventHub.mtx.RLock()
424+
defer eventHub.mtx.RUnlock()
425+
return eventHub.txRegistrants[txID]
426+
}
427+
366428
// getChainCodeEvents parses block events for chaincode events associated with individual transactions
367429
func getChainCodeEvent(tdata []byte) (*pb.ChaincodeEvent, error) {
368430

@@ -417,7 +479,7 @@ func getChainCodeEvent(tdata []byte) (*pb.ChaincodeEvent, error) {
417479
// Utility function to fire callbacks for chaincode registrants
418480
func (eventHub *eventHub) notifyChaincodeRegistrants(ccEvent *pb.ChaincodeEvent, patternMatch bool) {
419481

420-
cbeArray := eventHub.chaincodeRegistrants[ccEvent.ChaincodeId]
482+
cbeArray := eventHub.getChaincodeRegistrants(ccEvent.ChaincodeId)
421483
if len(cbeArray) <= 0 {
422484
logger.Debugf("No event registration for ccid %s \n", ccEvent.ChaincodeId)
423485
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/*
2+
Copyright SecureKey Technologies Inc. All Rights Reserved.
3+
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
*/
19+
20+
package events
21+
22+
import (
23+
"sync/atomic"
24+
"testing"
25+
26+
"fmt"
27+
28+
"time"
29+
30+
pb "github.com/hyperledger/fabric/protos/peer"
31+
)
32+
33+
func TestDeadlock(t *testing.T) {
34+
eventHub, clientFactory := createMockedEventHub(t)
35+
if t.Failed() {
36+
return
37+
}
38+
39+
fmt.Printf("EventHub Concurrency test\n")
40+
41+
client := clientFactory.clients[0]
42+
if client == nil {
43+
t.Fatalf("No client")
44+
}
45+
46+
threads := 20
47+
eventsPerThread := 100
48+
eventsSent := eventsPerThread * threads
49+
50+
// The test should be done in milliseconds but if there's
51+
// a deadlock then we don't want it to hang
52+
timeout := 5 * time.Second
53+
54+
// create a flood of TX events
55+
txCompletion := newMultiCompletionHandler(eventsSent, timeout)
56+
go flood(eventsPerThread, threads, func() {
57+
transactionID := generateTxID()
58+
received := newCompletionHandler(timeout)
59+
eventHub.RegisterTxEvent(transactionID, func(txID string, err error) {
60+
txCompletion.done()
61+
received.done()
62+
})
63+
64+
go client.MockEvent(&pb.Event{
65+
Event: buildMockTxEvent(transactionID),
66+
})
67+
68+
// Wait for the TX event and then unregister
69+
received.wait()
70+
eventHub.UnregisterTxEvent(transactionID)
71+
})
72+
73+
// create a flood of CC events
74+
ccCompletion := newMultiCompletionHandler(eventsSent, timeout)
75+
go flood(eventsPerThread, threads, func() {
76+
eventName := generateTxID()
77+
received := newCompletionHandler(timeout)
78+
registration := eventHub.RegisterChaincodeEvent("testccid", eventName, func(event *ChaincodeEvent) {
79+
ccCompletion.done()
80+
received.done()
81+
})
82+
83+
go client.MockEvent(&pb.Event{
84+
Event: buildMockCCEvent("testccid", eventName),
85+
})
86+
87+
// Wait for the CC event and then unregister
88+
received.wait()
89+
eventHub.UnregisterChaincodeEvent(registration)
90+
})
91+
92+
// Wait for all events to be received
93+
txCompletion.wait()
94+
ccCompletion.wait()
95+
96+
if txCompletion.numDone() != eventsSent {
97+
t.Errorf("Sent %d Tx events but received %d - could indicate a deadlock", eventsSent, txCompletion.numDone())
98+
} else {
99+
fmt.Printf("Received all %d TX events.\n", txCompletion.numDone())
100+
}
101+
102+
if ccCompletion.numDone() != eventsSent {
103+
t.Errorf("Sent %d CC events but received %d - could indicate a deadlock", eventsSent, ccCompletion.numDone())
104+
} else {
105+
fmt.Printf("Received all %d CC events.\n", ccCompletion.numDone())
106+
}
107+
}
108+
109+
// completionHandler waits for a single event with a timeout
110+
type completionHandler struct {
111+
completed chan bool
112+
timeout time.Duration
113+
}
114+
115+
// newCompletionHandler creates a new completionHandler
116+
func newCompletionHandler(timeout time.Duration) *completionHandler {
117+
return &completionHandler{
118+
timeout: timeout,
119+
completed: make(chan bool),
120+
}
121+
}
122+
123+
// wait will wait until the task(s) has completed or until the timeout
124+
func (c *completionHandler) wait() {
125+
select {
126+
case <-c.completed:
127+
case <-time.After(c.timeout):
128+
}
129+
}
130+
131+
// done marks the task as completed
132+
func (c *completionHandler) done() {
133+
c.completed <- true
134+
}
135+
136+
// multiCompletionHandler waits for multiple tasks to complete
137+
type multiCompletionHandler struct {
138+
completionHandler
139+
expected int32
140+
numCompleted int32
141+
}
142+
143+
// newMultiCompletionHandler creates a new multiCompletionHandler
144+
func newMultiCompletionHandler(expected int, timeout time.Duration) *multiCompletionHandler {
145+
return &multiCompletionHandler{
146+
expected: int32(expected),
147+
completionHandler: completionHandler{
148+
timeout: timeout,
149+
completed: make(chan bool),
150+
},
151+
}
152+
}
153+
154+
// done marks a task as completed
155+
func (c *multiCompletionHandler) done() {
156+
doneSoFar := atomic.AddInt32(&c.numCompleted, 1)
157+
if doneSoFar >= c.expected {
158+
c.completed <- true
159+
}
160+
}
161+
162+
// numDone returns the nmber of tasks that have completed
163+
func (c *multiCompletionHandler) numDone() int {
164+
return int(c.numCompleted)
165+
}
166+
167+
// flood invokes the given function in the given number of threads,
168+
// the given number of times per thread
169+
func flood(invocationsPerThread int, threads int, f func()) {
170+
for t := 0; t < threads; t++ {
171+
go func() {
172+
for i := 0; i < invocationsPerThread; i++ {
173+
f()
174+
}
175+
}()
176+
}
177+
}

0 commit comments

Comments
 (0)