Skip to content

Commit 1c925c4

Browse files
authored
Merge pull request #508 from ChIoT-Tech/master
Resolve send on closed channel when order=false and connection closed. Ref issue #505
2 parents 8e87e5f + 1917e39 commit 1c925c4

File tree

2 files changed

+116
-5
lines changed

2 files changed

+116
-5
lines changed

fvt_client_test.go

+64
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,70 @@ func Test_PublishEmptyMessage(t *testing.T) {
926926
s.Disconnect(250)
927927
}
928928

929+
// Test_CallbackOverrun - When ordermatters=false the callbacks are called within a go routine. It is possible that
930+
// the connection will drop before the handler completes and this should result in the ACK being dropped silently
931+
// (leads to a panic in v1.3-v1.3.4)
932+
func Test_CallbackOverrun(t *testing.T) {
933+
topic := "/test/callbackoverrun"
934+
handlerCalled := make(chan bool)
935+
handlerChoke := make(chan bool)
936+
handlerError := make(chan error)
937+
938+
pops := NewClientOptions()
939+
pops.AddBroker(FVTTCP)
940+
pops.SetOrderMatters(false) // Not really needed but consistent...
941+
pops.SetClientID("callbackoverrun-pub")
942+
p := NewClient(pops)
943+
944+
sops := NewClientOptions()
945+
sops.AddBroker(FVTTCP)
946+
sops.SetOrderMatters(false)
947+
sops.SetClientID("callbackoverrun-sub")
948+
var f MessageHandler = func(client Client, msg Message) {
949+
handlerCalled <- true
950+
<-handlerChoke // Wait until connection has been closed
951+
if string(msg.Payload()) != "test message" {
952+
handlerError <- fmt.Errorf("Message payload incorrect")
953+
} else {
954+
handlerError <- nil // Allow main test to proceed (should not raise error in go routine)
955+
}
956+
}
957+
958+
s := NewClient(sops).(*client)
959+
if sToken := s.Connect(); sToken.Wait() && sToken.Error() != nil {
960+
t.Fatalf("Error on Client.Connect(): %v", sToken.Error())
961+
}
962+
963+
if sToken := s.Subscribe(topic, 1, f); sToken.Wait() && sToken.Error() != nil {
964+
t.Fatalf("Error on Client.Subscribe(): %v", sToken.Error())
965+
}
966+
967+
if pToken := p.Connect(); pToken.Wait() && pToken.Error() != nil {
968+
t.Fatalf("Error on Client.Connect(): %v", pToken.Error())
969+
}
970+
971+
p.Publish(topic, 1, false, "test message")
972+
wait(handlerCalled) // Wait until the handler has been called
973+
s.Disconnect(250) // Ensure the connection is dropped
974+
<-s.commsStopped // Double check...
975+
handlerChoke <- true // Allow handler to proceed
976+
977+
err := <-handlerError
978+
if err != nil {
979+
t.Fatalf(err.Error())
980+
}
981+
982+
time.Sleep(time.Microsecond) // Allow a little time in case the handler returning after connection dropped causes an issue (panic)
983+
fmt.Println("reconnecting")
984+
// Now attempt to reconnect (checking for blockages)
985+
if sToken := s.Connect(); sToken.Wait() && sToken.Error() != nil {
986+
t.Fatalf("Error on Client.Connect(): %v", sToken.Error())
987+
}
988+
989+
s.Disconnect(250)
990+
p.Disconnect(250)
991+
}
992+
929993
// func Test_Cleanstore(t *testing.T) {
930994
// store := "/tmp/fvt/cleanstore"
931995
// topic := "/test/cleanstore"

router.go

+52-5
Original file line numberDiff line numberDiff line change
@@ -132,23 +132,58 @@ func (r *router) setDefaultHandler(handler MessageHandler) {
132132
// associated callback (or the defaultHandler, if one exists and no other route matched). If
133133
// anything is sent down the stop channel the function will end.
134134
func (r *router) matchAndDispatch(messages <-chan *packets.PublishPacket, order bool, client *client) <-chan *PacketAndToken {
135-
ackChan := make(chan *PacketAndToken)
136-
go func() {
135+
var wg sync.WaitGroup
136+
ackOutChan := make(chan *PacketAndToken) // Channel returned to caller; closed when messages channel closed
137+
var ackInChan chan *PacketAndToken // ACKs generated by ackFunc get put onto this channel
138+
139+
stopAckCopy := make(chan struct{}) // Closure requests stop of go routine copying ackInChan to ackOutChan
140+
ackCopyStopped := make(chan struct{}) // Closure indicates that it is safe to close ackOutChan
141+
goRoutinesDone := make(chan struct{}) // closed on wg.Done()
142+
if order {
143+
ackInChan = ackOutChan // When order = true no go routines are used so safe to use one channel and close when done
144+
} else {
145+
// When order = false ACK messages are sent in go routines so ackInChan cannot be closed until all goroutines done
146+
ackInChan = make(chan *PacketAndToken)
147+
go func() { // go routine to copy from ackInChan to ackOutChan until stopped
148+
for {
149+
select {
150+
case a := <-ackInChan:
151+
ackOutChan <- a
152+
case <-stopAckCopy:
153+
close(ackCopyStopped) // Signal main go routine that it is safe to close ackOutChan
154+
for {
155+
select {
156+
case <-ackInChan: // drain ackInChan to ensure all goRoutines can complete cleanly (ACK dropped)
157+
DEBUG.Println(ROU, "matchAndDispatch received acknowledgment after processing stopped (ACK dropped).")
158+
case <-goRoutinesDone:
159+
close(ackInChan) // Nothing further should be sent (a panic is probably better than silent failure)
160+
DEBUG.Println(ROU, "matchAndDispatch order=false copy goroutine exiting.")
161+
return
162+
}
163+
}
164+
}
165+
}
166+
}()
167+
}
168+
169+
go func() { // Main go routine handling inbound messages
137170
for message := range messages {
138171
// DEBUG.Println(ROU, "matchAndDispatch received message")
139172
sent := false
140173
r.RLock()
141-
m := messageFromPublish(message, ackFunc(ackChan, client.persist, message))
174+
m := messageFromPublish(message, ackFunc(ackInChan, client.persist, message))
142175
var handlers []MessageHandler
143176
for e := r.routes.Front(); e != nil; e = e.Next() {
144177
if e.Value.(*route).match(message.TopicName) {
145178
if order {
146179
handlers = append(handlers, e.Value.(*route).callback)
147180
} else {
148181
hd := e.Value.(*route).callback
182+
wg.Add(1)
149183
go func() {
150184
hd(client, m)
151185
m.Ack()
186+
wg.Done()
152187
}()
153188
}
154189
sent = true
@@ -159,9 +194,11 @@ func (r *router) matchAndDispatch(messages <-chan *packets.PublishPacket, order
159194
if order {
160195
handlers = append(handlers, r.defaultHandler)
161196
} else {
197+
wg.Add(1)
162198
go func() {
163199
r.defaultHandler(client, m)
164200
m.Ack()
201+
wg.Done()
165202
}()
166203
}
167204
} else {
@@ -175,8 +212,18 @@ func (r *router) matchAndDispatch(messages <-chan *packets.PublishPacket, order
175212
}
176213
// DEBUG.Println(ROU, "matchAndDispatch handled message")
177214
}
178-
close(ackChan)
215+
if order {
216+
close(ackOutChan)
217+
} else { // Ensure that nothing further will be written to ackOutChan before closing it
218+
close(stopAckCopy)
219+
<-ackCopyStopped
220+
close(ackOutChan)
221+
go func() {
222+
wg.Wait() // Note: If this remains running then the user has handlers that are not returning
223+
close(goRoutinesDone)
224+
}()
225+
}
179226
DEBUG.Println(ROU, "matchAndDispatch exiting")
180227
}()
181-
return ackChan
228+
return ackOutChan
182229
}

0 commit comments

Comments
 (0)