@@ -8,19 +8,22 @@ package fabric
88
99import (
1010 "context"
11+ "runtime"
1112 "sync"
1213 "testing"
1314 "time"
1415
1516 "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/core/generic/committer"
1617 "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/events"
1718 "github.com/stretchr/testify/assert"
19+ "github.com/stretchr/testify/require"
1820)
1921
2022const (
2123 publicationSnooze = time .Millisecond
2224 timeout = 75 * time .Millisecond
23- waitFor = 4 * timeout
25+ longTimeout = 1 * time .Minute
26+ waitFor = 1 * timeout
2427 tick = timeout / 10
2528)
2629
@@ -43,9 +46,11 @@ func (m *mockSubscriber) Unsubscribe(chaincodeName string, listener events.Liste
4346
4447func (m * mockSubscriber ) Publish (chaincodeName string , event * committer.ChaincodeEvent ) {
4548 m .m .RLock ()
46- defer m .m .RUnlock ()
47- if m .listener != nil {
48- m .listener .OnReceive (event )
49+ l := m .listener
50+ m .m .RUnlock ()
51+
52+ if l != nil {
53+ l .OnReceive (event )
4954 }
5055}
5156
@@ -54,30 +59,32 @@ func TestEventListener(t *testing.T) {
5459 listener := newEventListener (subscriber , "testChaincode" )
5560 ch := listener .ChaincodeEvents ()
5661
57- done := make (chan bool )
62+ msg := & committer.ChaincodeEvent {Payload : []byte ("some msg" )}
63+ stopPublisher := make (chan bool )
5864
5965 var wg sync.WaitGroup
60- wg .Add (2 )
6166
6267 // Publish events
68+ wg .Add (1 )
6369 go func () {
6470 defer wg .Done ()
65- msg := []byte ("some msg" )
6671 for {
6772 select {
68- case <- done :
73+ case <- stopPublisher :
6974 return
7075 default :
71- subscriber .Publish ("testChaincode" , & committer. ChaincodeEvent { Payload : msg } )
76+ subscriber .Publish ("testChaincode" , msg )
7277 time .Sleep (publicationSnooze )
7378 }
7479 }
7580 }()
7681
77- // Stop while the publisher is still publishing
78- ctx , cancel := context .WithTimeout (context .Background (), timeout )
82+ // Stop the consumer and close the event listener while the producer is still publishing
83+ ctx , cancel := context .WithTimeout (context .Background (), waitFor )
7984 defer cancel ()
8085
86+ // Consumer
87+ wg .Add (1 )
8188 go func () {
8289 defer wg .Done ()
8390 for {
@@ -94,13 +101,138 @@ func TestEventListener(t *testing.T) {
94101 }
95102 }()
96103
97- assert .Eventually (t , func () bool {
98- // eventually our event should be closed
104+ // let's wait until our timeout is fired
105+ <- ctx .Done ()
106+
107+ // consume everything that is remaining in ch and eventually the channel should be closed
108+ require .Eventually (t , func () bool {
99109 _ , ok := <- ch
100110 return ! ok
101111 }, waitFor , tick )
102112
103- // now we let our consumer know that they can stop working
104- close (done )
113+ // now we let our publisher know that they can stop working
114+ close (stopPublisher )
105115 wg .Wait ()
116+
117+ // check that our channel is closed
118+ require .Eventually (t , func () bool {
119+ return isClosed (ch )
120+ }, timeout , tick )
121+ }
122+
123+ func TestEventServiceMultipleClose (t * testing.T ) {
124+ subscriber := & mockSubscriber {}
125+ listener := newEventListener (subscriber , "testChaincode" )
126+ ch := listener .ChaincodeEvents ()
127+ msg1 := & committer.ChaincodeEvent {Payload : []byte ("msg1" )}
128+
129+ var wg sync.WaitGroup
130+ wg .Add (1 )
131+ go func () {
132+ wg .Done ()
133+ subscriber .Publish ("testChaincode" , msg1 )
134+ listener .CloseChaincodeEvents ()
135+ }()
136+
137+ // Call Close multiple times safely
138+ listener .CloseChaincodeEvents ()
139+ listener .CloseChaincodeEvents ()
140+ listener .CloseChaincodeEvents ()
141+
142+ wg .Wait ()
143+
144+ // check that our channel is closed
145+ require .Eventually (t , func () bool {
146+ return isClosed (ch )
147+ }, timeout , tick )
148+ }
149+
150+ func TestEventListenerDeadlock (t * testing.T ) {
151+ subscriber := & mockSubscriber {}
152+
153+ const customBufferLen = 1
154+
155+ // in this test we configure our event listener with a smaller buffer and long recvTimeout
156+ listener := & EventListener {
157+ chaincodeName : "testChaincode" ,
158+ subscriber : subscriber ,
159+ eventCh : make (chan * committer.ChaincodeEvent , customBufferLen ),
160+ middleCh : make (chan * committer.ChaincodeEvent ),
161+ closing : make (chan struct {}),
162+ closed : make (chan struct {}),
163+ recvTimeout : longTimeout ,
164+ }
165+
166+ ch := listener .ChaincodeEvents ()
167+
168+ msg1 := & committer.ChaincodeEvent {Payload : []byte ("msg1" )}
169+ msg2 := & committer.ChaincodeEvent {Payload : []byte ("msg2" )}
170+
171+ // we publish and then consume
172+ subscriber .Publish ("testChaincode" , msg1 )
173+ require .EventuallyWithT (t , func (ct * assert.CollectT ) {
174+ require .Len (ct , ch , 1 )
175+ require .Equal (ct , msg1 , <- ch )
176+ require .Len (ct , ch , 0 )
177+ }, timeout , tick )
178+
179+ // next up, we fill our event buffer by publishing msg1
180+ for range customBufferLen {
181+ subscriber .Publish ("testChaincode" , msg1 )
182+ }
183+ require .EventuallyWithT (t , func (ct * assert.CollectT ) {
184+ // out channel should be full now
185+ require .Len (ct , ch , customBufferLen )
186+ }, timeout , tick )
187+
188+ require .Never (t , func () bool {
189+ // this should be blocking (until longTimeout is fired)
190+ subscriber .Publish ("testChaincode" , msg1 )
191+ return false
192+ }, timeout , tick )
193+
194+ // we kick off our producer to publish msg2
195+ var wg sync.WaitGroup
196+ wg .Add (1 )
197+ go func () {
198+ defer wg .Done ()
199+ // as msg1 is not yet consumed, our producer is blocked
200+ subscriber .Publish ("testChaincode" , msg2 )
201+ }()
202+
203+ // let's give the producer a bit time
204+ runtime .Gosched ()
205+ time .Sleep (waitFor )
206+
207+ // let's make sure that our producer is still waiting to complete publish msg2
208+ require .Never (t , func () bool {
209+ // we expect to be blocked
210+ wg .Wait ()
211+ return false
212+ }, timeout , tick )
213+
214+ // now, we close the listener, which should unblock the producer
215+ listener .CloseChaincodeEvents ()
216+
217+ // wait for the producer to finish
218+ wg .Wait ()
219+
220+ // we expect msg1 to be successfully published
221+ require .EventuallyWithT (t , func (c * assert.CollectT ) {
222+ require .Equal (c , msg1 , <- ch )
223+ }, timeout , tick )
224+
225+ // check that our channel is closed
226+ require .Eventually (t , func () bool {
227+ return isClosed (ch )
228+ }, timeout , tick )
229+ }
230+
231+ func isClosed (ch <- chan * committer.ChaincodeEvent ) bool {
232+ select {
233+ case <- ch :
234+ return true
235+ default :
236+ }
237+ return false
106238}
0 commit comments