Skip to content

Commit 202c400

Browse files
committed
Move sent test request id into the active session state
1 parent 489494f commit 202c400

3 files changed

Lines changed: 51 additions & 14 deletions

File tree

crates/hotfix/src/session.rs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use crate::message::sequence_reset::SequenceReset;
3131
use crate::message::test_request::TestRequest;
3232
use crate::message_utils::is_admin;
3333
use crate::session::event::AwaitingActiveSessionResponse;
34-
use crate::session::state::AwaitingResendState;
34+
use crate::session::state::{AwaitingResendState, TestRequestId};
3535
use crate::session_schedule::SessionSchedule;
3636
use event::SessionEvent;
3737
use hotfix_message::parsed_message::ParsedMessage;
@@ -44,8 +44,6 @@ pub struct SessionRef<M> {
4444
sender: mpsc::Sender<SessionEvent<M>>,
4545
}
4646

47-
type TestRequestId = String;
48-
4947
impl<M: FixMessage> SessionRef<M> {
5048
pub fn new(
5149
config: SessionConfig,
@@ -117,7 +115,6 @@ struct Session<M, S> {
117115
state: SessionState,
118116
application: ApplicationRef<M>,
119117
store: S,
120-
awaiting_test_response: Option<TestRequestId>,
121118
schedule_check_timer: Pin<Box<Sleep>>,
122119
}
123120

@@ -142,7 +139,6 @@ impl<M: FixMessage, S: MessageStore> Session<M, S> {
142139
state: SessionState::new_disconnected(true, "initialising"),
143140
application,
144141
store,
145-
awaiting_test_response: None,
146142
schedule_check_timer: Box::pin(schedule_check_timer),
147143
}
148144
}
@@ -159,7 +155,7 @@ impl<M: FixMessage, S: MessageStore> Session<M, S> {
159155

160156
async fn on_incoming(&mut self, raw_message: RawFixMessage) -> Result<()> {
161157
debug!("received message: {}", raw_message);
162-
if self.awaiting_test_response.is_none() {
158+
if !self.state.is_expecting_test_response() {
163159
// if we are not awaiting a specific test response, any message can reset the timer
164160
// otherwise, only a heartbeat with the corresponding TestReqID can
165161
self.reset_peer_timer(None);
@@ -398,7 +394,7 @@ impl<M: FixMessage, S: MessageStore> Session<M, S> {
398394

399395
async fn on_heartbeat(&mut self, message: &Message) {
400396
if let (Some(expected_req_id), Ok(message_req_id)) = (
401-
&self.awaiting_test_response,
397+
&self.state.expected_test_response_id(),
402398
message.get::<&str>(fix44::TEST_REQ_ID),
403399
) {
404400
if expected_req_id.as_str() == message_req_id {
@@ -536,9 +532,9 @@ impl<M: FixMessage, S: MessageStore> Session<M, S> {
536532
.reset_heartbeat_timer(self.config.heartbeat_interval);
537533
}
538534

539-
fn reset_peer_timer(&mut self, awaiting_req_id: Option<TestRequestId>) {
540-
self.awaiting_test_response = awaiting_req_id;
541-
self.state.reset_peer_timer(self.config.heartbeat_interval);
535+
fn reset_peer_timer(&mut self, test_request_id: Option<TestRequestId>) {
536+
self.state
537+
.reset_peer_timer(self.config.heartbeat_interval, test_request_id);
542538
}
543539

544540
async fn send_message(&mut self, message: impl FixMessage) {
@@ -651,7 +647,7 @@ impl<M: FixMessage, S: MessageStore> Session<M, S> {
651647
}
652648

653649
async fn handle_peer_timeout(&mut self) {
654-
if self.awaiting_test_response.is_some() {
650+
if self.state.is_expecting_test_response() {
655651
warn!("peer didn't respond, terminating..");
656652
self.logout_and_terminate("peer timeout").await;
657653
} else {

crates/hotfix/src/session/state.rs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use tracing::{debug, error};
1010

1111
const TEST_REQUEST_THRESHOLD: f64 = 1.2;
1212

13+
pub(crate) type TestRequestId = String;
14+
1315
pub enum SessionState {
1416
/// We have established a connection, sent a logon message and await a response.
1517
AwaitingLogon { writer: WriterRef, logon_sent: bool },
@@ -40,6 +42,7 @@ impl SessionState {
4042
writer,
4143
heartbeat_deadline: Instant::now() + Duration::from_secs(heartbeat_interval),
4244
peer_deadline: Instant::now() + Duration::from_secs(peer_interval),
45+
sent_test_request_id: None,
4346
})
4447
}
4548

@@ -178,12 +181,36 @@ impl SessionState {
178181
}
179182
}
180183

181-
pub fn reset_peer_timer(&mut self, heartbeat_interval: u64) {
182-
if let Self::Active(ActiveState { peer_deadline, .. }) = self {
184+
pub fn reset_peer_timer(
185+
&mut self,
186+
heartbeat_interval: u64,
187+
test_request_id: Option<TestRequestId>,
188+
) {
189+
if let Self::Active(ActiveState {
190+
peer_deadline,
191+
sent_test_request_id,
192+
..
193+
}) = self
194+
{
183195
let interval = calculate_peer_interval(heartbeat_interval);
184196
*peer_deadline = Instant::now() + Duration::from_secs(interval);
197+
*sent_test_request_id = test_request_id;
198+
}
199+
}
200+
201+
pub fn expected_test_response_id(&self) -> Option<&TestRequestId> {
202+
match self {
203+
Self::Active(ActiveState {
204+
sent_test_request_id: expected_test_response_id,
205+
..
206+
}) => expected_test_response_id.as_ref(),
207+
_ => None,
185208
}
186209
}
210+
211+
pub fn is_expecting_test_response(&self) -> bool {
212+
self.expected_test_response_id().is_some()
213+
}
187214
}
188215

189216
#[inline]
@@ -192,9 +219,14 @@ fn calculate_peer_interval(heartbeat_interval: u64) -> u64 {
192219
}
193220

194221
pub struct ActiveState {
222+
/// The writer's reference to send messages to the counterparty
195223
writer: WriterRef,
224+
/// When we should send the next heartbeat message to the counterparty
196225
heartbeat_deadline: Instant,
226+
/// When the next message from the counterparty is expected at the latest
197227
peer_deadline: Instant,
228+
/// The ID of the test request we sent on peer timer expiry
229+
sent_test_request_id: Option<TestRequestId>,
198230
}
199231

200232
/// Session state we're in while processing messages we requested to be resent.

crates/hotfix/tests/session_tests.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,17 @@ async fn test_peer_timeout() {
6767
mock_counterparty.send_logon().await;
6868
tokio::task::yield_now().await;
6969

70+
// let's wait enough time for a heartbeat and assert that the heartbeat was sent
71+
tokio::time::advance(std::time::Duration::from_secs(HEARTBEAT_INTERVAL + 1)).await;
72+
mock_counterparty
73+
.assert_next(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "0"))
74+
.await;
75+
7076
// we wait enough time for the peer deadline to pass
71-
tokio::time::advance(std::time::Duration::from_secs(peer_interval)).await;
77+
tokio::time::advance(std::time::Duration::from_secs(
78+
peer_interval - HEARTBEAT_INTERVAL,
79+
))
80+
.await;
7281
// a TestRequest (type '1') is sent to the counterparty
7382
mock_counterparty
7483
.assert_next(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "1"))

0 commit comments

Comments
 (0)