Skip to content

Commit 3939268

Browse files
committed
Fix stuck call to Dial when calling Stop on the initiator
This commit fixes an issue when calling Start() and then Stop() on the initiator while the connection is likely to fail and timeout. Calling initiator.Stop() will block since Dial will attempt to connect until it times out and returns on the 'waitForReconnectInterval' call. We mitigate this problem by using a proxy.ContextDialer and allowing to pass a context with cancellation method to the dialer.DialContext method on 'handleConnection'. We need to start a routine listening for the stopChan in order to call cancel() explicitly and thus exit the DialContext method. Note: there are scenarios where cancel() will be called twice, this choice was made in order to avoid a larger refactor of the reconnect logic, but since the call to cancel() is idempotent, this doesn't lead to any adverse effect. fixes #653 Signed-off-by: Alexandre Beslic <ab21c8b9f7@abronan.com>
1 parent e3a2994 commit 3939268

File tree

2 files changed

+39
-5
lines changed

2 files changed

+39
-5
lines changed

dialer.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525
"github.com/quickfixgo/quickfix/config"
2626
)
2727

28-
func loadDialerConfig(settings *SessionSettings) (dialer proxy.Dialer, err error) {
28+
func loadDialerConfig(settings *SessionSettings) (dialer proxy.ContextDialer, err error) {
2929
stdDialer := &net.Dialer{}
3030
if settings.HasSetting(config.SocketTimeout) {
3131
timeout, err := settings.DurationSetting(config.SocketTimeout)
@@ -73,9 +73,23 @@ func loadDialerConfig(settings *SessionSettings) (dialer proxy.Dialer, err error
7373
}
7474
}
7575

76-
dialer, err = proxy.SOCKS5("tcp", fmt.Sprintf("%s:%d", proxyHost, proxyPort), proxyAuth, dialer)
76+
var proxyDialer proxy.Dialer
77+
78+
proxyDialer, err = proxy.SOCKS5("tcp", fmt.Sprintf("%s:%d", proxyHost, proxyPort), proxyAuth, stdDialer)
79+
if err != nil {
80+
return
81+
}
82+
83+
if contextDialer, ok := proxyDialer.(proxy.ContextDialer); ok {
84+
dialer = contextDialer
85+
} else {
86+
err = fmt.Errorf("proxy does not support context dialer")
87+
return
88+
}
89+
7790
default:
7891
err = fmt.Errorf("unsupported proxy type %s", proxyType)
7992
}
93+
8094
return
8195
}

initiator.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package quickfix
1717

1818
import (
1919
"bufio"
20+
"context"
2021
"crypto/tls"
2122
"strings"
2223
"sync"
@@ -50,7 +51,7 @@ func (i *Initiator) Start() (err error) {
5051
return
5152
}
5253

53-
var dialer proxy.Dialer
54+
var dialer proxy.ContextDialer
5455
if dialer, err = loadDialerConfig(settings); err != nil {
5556
return
5657
}
@@ -142,7 +143,7 @@ func (i *Initiator) waitForReconnectInterval(reconnectInterval time.Duration) bo
142143
return true
143144
}
144145

145-
func (i *Initiator) handleConnection(session *session, tlsConfig *tls.Config, dialer proxy.Dialer) {
146+
func (i *Initiator) handleConnection(session *session, tlsConfig *tls.Config, dialer proxy.ContextDialer) {
146147
var wg sync.WaitGroup
147148
wg.Add(1)
148149
go func() {
@@ -162,14 +163,27 @@ func (i *Initiator) handleConnection(session *session, tlsConfig *tls.Config, di
162163
return
163164
}
164165

166+
ctx, cancel := context.WithCancel(context.Background())
167+
168+
// We start a goroutine in order to be able to cancel the dialer mid-connection
169+
// on receiving a stop signal to stop the initiator.
170+
go func() {
171+
select {
172+
case <-i.stopChan:
173+
cancel()
174+
case <-ctx.Done():
175+
return
176+
}
177+
}()
178+
165179
var disconnected chan interface{}
166180
var msgIn chan fixIn
167181
var msgOut chan []byte
168182

169183
address := session.SocketConnectAddress[connectionAttempt%len(session.SocketConnectAddress)]
170184
session.log.OnEventf("Connecting to: %v", address)
171185

172-
netConn, err := dialer.Dial("tcp", address)
186+
netConn, err := dialer.DialContext(ctx, "tcp", address)
173187
if err != nil {
174188
session.log.OnEventf("Failed to connect: %v", err)
175189
goto reconnect
@@ -208,13 +222,19 @@ func (i *Initiator) handleConnection(session *session, tlsConfig *tls.Config, di
208222
close(disconnected)
209223
}()
210224

225+
// This ensures we properly cleanup the goroutine and context used for
226+
// dial cancelation after successful connection.
227+
cancel()
228+
211229
select {
212230
case <-disconnected:
213231
case <-i.stopChan:
214232
return
215233
}
216234

217235
reconnect:
236+
cancel()
237+
218238
connectionAttempt++
219239
session.log.OnEventf("Reconnecting in %v", session.ReconnectInterval)
220240
if !i.waitForReconnectInterval(session.ReconnectInterval) {

0 commit comments

Comments
 (0)