diff --git a/in_session.go b/in_session.go index 5445967cd..0ed53200c 100644 --- a/in_session.go +++ b/in_session.go @@ -230,6 +230,11 @@ func (state inSession) resendMessages(session *session, beginSeqNo, endSeqNo int return state.generateSequenceReset(session, beginSeqNo, endSeqNo+1, inReplyTo) } + // resendMutex must always be locked before sendMutex to prevent a potential deadlock + // sendMutex is locked below in session.EnqueueBytesAndSend() + session.resendMutex.Lock() + defer session.resendMutex.Unlock() + seqNum := beginSeqNo nextSeqNum := seqNum msg := NewMessage() diff --git a/in_session_test.go b/in_session_test.go index 8973e4ef0..d20979508 100644 --- a/in_session_test.go +++ b/in_session_test.go @@ -19,6 +19,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" "github.com/quickfixgo/quickfix/internal" @@ -373,6 +374,25 @@ func (s *InSessionTestSuite) TestFIXMsgInResendRequestDoNotSendApp() { s.State(inSession{}) } +func (s *InSessionTestSuite) TestFIXMsgInResendRequestBlocksSend() { + s.MockApp.On("ToApp").Return(nil) + s.Require().Nil(s.session.send(s.NewOrderSingle())) + s.LastToAppMessageSent() + s.MockApp.AssertNumberOfCalls(s.T(), "ToApp", 1) + s.NextSenderMsgSeqNum(2) + + s.MockStore.On("IterateMessages", mock.Anything, mock.Anything, mock.AnythingOfType("func([]byte) error")). + Run(func(_ mock.Arguments) { + s.Require().Nil(s.session.send(s.NewOrderSingle())) + }). + Return(nil) + + s.MockApp.On("FromAdmin").Return(nil) + s.fixMsgIn(s.session, s.ResendRequest(1)) + + s.NextSenderMsgSeqNum(2) +} + func (s *InSessionTestSuite) TestFIXMsgInTargetTooLow() { s.IncrNextTargetMsgSeqNum() diff --git a/session.go b/session.go index 8213f8486..35281e98e 100644 --- a/session.go +++ b/session.go @@ -41,6 +41,9 @@ type session struct { // Mutex for access to toSend. sendMutex sync.Mutex + // Mutex to prevent messages being sent when resendRequest is active + // Must be locked before sendMutex to prevent a potential deadlock + resendMutex sync.RWMutex sessionEvent chan internal.Event messageEvent chan bool @@ -302,6 +305,10 @@ func (s *session) sendInReplyTo(msg *Message, inReplyTo *Message) error { return s.queueForSend(msg) } + // resendMutex must always be locked before sendMutex to prevent a potential deadlock + s.resendMutex.RLock() + defer s.resendMutex.RUnlock() + s.sendMutex.Lock() defer s.sendMutex.Unlock()