1use std::{
2 any::Any,
3 cell::{Cell, RefCell},
4 collections::{HashMap, hash_map::Entry},
5 future::Future,
6 panic::AssertUnwindSafe,
7 rc::Rc,
8 sync::Mutex,
9 time::Duration,
10};
11
12use easyfix_messages::{
13 fields::{FixString, SessionStatus},
14 messages::{FixtMessage, Message},
15};
16use futures_util::{FutureExt, Stream, pin_mut};
17use tokio::{
18 self,
19 io::{AsyncRead, AsyncWrite, AsyncWriteExt},
20 net::TcpStream,
21 sync::{mpsc, oneshot},
22};
23use tokio_stream::StreamExt;
24use tracing::{Instrument, Span, debug, error, info, info_span, warn};
25
26use crate::{
27 DisconnectReason, Error, NO_INBOUND_TIMEOUT_PADDING, Sender, SessionError,
28 TEST_REQUEST_THRESHOLD,
29 acceptor::{ActiveSessionsMap, SessionsMap},
30 application::{Emitter, FixEventInternal},
31 messages_storage::MessagesStorage,
32 session::Session,
33 session_id::SessionId,
34 session_state::State,
35 settings::{SessionSettings, Settings},
36};
37
38mod input_stream;
39pub use input_stream::{InputEvent, InputStream, input_stream};
40
41mod output_stream;
42use output_stream::{OutputEvent, output_stream};
43
44pub mod time;
45use time::{timeout, timeout_at, timeout_stream};
46
47static SENDERS: Mutex<Option<HashMap<SessionId, Sender>>> = Mutex::new(None);
48
49pub fn register_sender(session_id: SessionId, sender: Sender) {
50 if let Entry::Vacant(entry) = SENDERS
51 .lock()
52 .unwrap()
53 .get_or_insert_with(HashMap::new)
54 .entry(session_id)
55 {
56 entry.insert(sender);
57 }
58}
59
60pub fn unregister_sender(session_id: &SessionId) {
61 if SENDERS
62 .lock()
63 .unwrap()
64 .get_or_insert_with(HashMap::new)
65 .remove(session_id)
66 .is_none()
67 {
68 }
70}
71
72pub fn sender(session_id: &SessionId) -> Option<Sender> {
73 SENDERS
74 .lock()
75 .unwrap()
76 .get_or_insert_with(HashMap::new)
77 .get(session_id)
78 .cloned()
79}
80
81pub fn send(session_id: &SessionId, msg: Box<Message>) -> Result<(), Box<Message>> {
83 if let Some(sender) = sender(session_id) {
84 sender.send(msg).map_err(|msg| msg.body)
85 } else {
86 Err(msg)
87 }
88}
89
90pub fn send_raw(msg: Box<FixtMessage>) -> Result<(), Box<FixtMessage>> {
91 if let Some(sender) = sender(&SessionId::from_input_msg(&msg)) {
92 sender.send_raw(msg)
93 } else {
94 Err(msg)
95 }
96}
97
98async fn first_msg(
99 stream: &mut (impl Stream<Item = InputEvent> + Unpin),
100 logon_timeout: Duration,
101) -> Result<Box<FixtMessage>, Error> {
102 match timeout(logon_timeout, stream.next()).await {
103 Ok(Some(InputEvent::Message(msg))) => Ok(msg),
104 Ok(Some(InputEvent::IoError(error))) => Err(error.into()),
105 Ok(Some(InputEvent::DeserializeError(error))) => {
106 error!("failed to deserialize first message: {error}");
107 Err(Error::SessionError(SessionError::LogonNeverReceived))
108 }
109 _ => Err(Error::SessionError(SessionError::LogonNeverReceived)),
110 }
111}
112
113#[derive(Debug)]
114struct Connection<S> {
115 session: Rc<Session<S>>,
116}
117
118pub(crate) type PendingLogout = Rc<RefCell<Option<(SessionId, DisconnectReason)>>>;
122
123fn panic_message(panic: &(dyn Any + Send)) -> &str {
124 if let Some(message) = panic.downcast_ref::<&str>() {
125 message
126 } else if let Some(message) = panic.downcast_ref::<String>() {
127 message
128 } else {
129 "<non-string panic payload>"
130 }
131}
132
133pub(crate) async fn supervise_connection(
141 connection: impl Future<Output = ()>,
142 pending_logout: PendingLogout,
143 emitter: &Emitter,
144) {
145 if let Err(panic) = AssertUnwindSafe(connection).catch_unwind().await {
150 error!(
151 "connection task panicked: {}",
152 panic_message(panic.as_ref())
153 );
154 let parked_logout = pending_logout.borrow_mut().take();
155 if let Some((session_id, reason)) = parked_logout {
156 emitter
157 .send(FixEventInternal::Logout(session_id, reason))
158 .await;
159 }
160 }
161}
162
163struct SessionCleanupGuard<S: MessagesStorage> {
169 session_id: SessionId,
170 state: Rc<RefCell<State<S>>>,
171 active_sessions: Rc<RefCell<ActiveSessionsMap<S>>>,
172 pending_logout: PendingLogout,
173 reset_on_disconnect: bool,
174}
175
176impl<S: MessagesStorage> Drop for SessionCleanupGuard<S> {
177 fn drop(&mut self) {
178 unregister_sender(&self.session_id);
179
180 match self.active_sessions.try_borrow_mut() {
182 Ok(mut active_sessions) => {
183 active_sessions.remove(&self.session_id);
184 }
185 Err(_) => error!(
186 session_id = %self.session_id,
187 "session cleanup failed: active sessions map already borrowed"
188 ),
189 }
190
191 match self.state.try_borrow_mut() {
192 Ok(mut state) => {
193 let logout_not_emitted = state.logon_received() || state.logon_sent();
199 state.set_logon_received(false);
200 state.set_logon_sent(false);
201 if !state.disconnected() {
202 warn!(
203 session_id = %self.session_id,
204 "connection task finished without disconnecting, forcing disconnected state"
205 );
206 state.disconnect(self.reset_on_disconnect);
207 }
208 if logout_not_emitted {
209 *self.pending_logout.borrow_mut() =
212 Some((self.session_id.clone(), DisconnectReason::Disconnected));
213 }
214 }
215 Err(_) => error!(
216 session_id = %self.session_id,
217 "session cleanup failed: session state already borrowed"
218 ),
219 }
220 }
221}
222
223#[expect(clippy::too_many_arguments)]
224pub(crate) async fn acceptor_connection<S>(
225 reader: impl AsyncRead + Unpin,
226 writer: impl AsyncWrite + Unpin,
227 settings: Settings,
228 sessions: Rc<RefCell<SessionsMap<S>>>,
229 active_sessions: Rc<RefCell<ActiveSessionsMap<S>>>,
230 emitter: Emitter,
231 enabled: Rc<Cell<bool>>,
232 pending_logout: PendingLogout,
233) where
234 S: MessagesStorage,
235{
236 let stream = input_stream(reader);
237 let logon_timeout =
238 settings.auto_disconnect_after_no_logon_received + NO_INBOUND_TIMEOUT_PADDING;
239 pin_mut!(stream);
240 let msg = match first_msg(&mut stream, logon_timeout).await {
241 Ok(msg) => msg,
242 Err(err) => {
243 error!(%err, "failed to establish new session");
244 return;
245 }
246 };
247
248 let session_id = SessionId::from_input_msg(&msg);
249 debug!(first_msg = ?msg);
250
251 if !enabled.get() {
253 warn!("Acceptor is disabled, drop connection");
254 return;
255 }
256
257 let (sender, receiver) = mpsc::unbounded_channel();
258 let sender = Sender::new(sender);
259
260 let Some((session_settings, session_state)) = sessions.borrow().get_session(&session_id) else {
261 error!(%session_id, "failed to establish new session: unknown session id");
262 return;
263 };
264 if !session_state.borrow_mut().disconnected()
265 || active_sessions.borrow().contains_key(&session_id)
266 {
267 error!(%session_id, "Session already active");
268 return;
269 }
270 session_state.borrow_mut().set_disconnected(false);
271 register_sender(session_id.clone(), sender.clone());
272
273 let _cleanup_guard = SessionCleanupGuard {
274 session_id: session_id.clone(),
275 state: session_state.clone(),
276 active_sessions: active_sessions.clone(),
277 pending_logout,
278 reset_on_disconnect: session_settings.reset_on_disconnect,
279 };
280
281 let (disconnect_tx, disconnect_rx) = oneshot::channel();
282
283 let session = Rc::new(Session::new(
284 settings,
285 session_settings,
286 session_state,
287 sender,
288 emitter.clone(),
289 disconnect_tx,
290 ));
291
292 active_sessions
293 .borrow_mut()
294 .insert(session_id.clone(), session.clone());
295
296 let session_span = info_span!(
297 parent: None,
298 "session",
299 id = %session_id
300 );
301 session_span.follows_from(Span::current());
302
303 let input_loop_span = info_span!(parent: &session_span, "in");
304 let output_loop_span = info_span!(parent: &session_span, "out");
305
306 let force_disconnection_with_reason = session
307 .on_message_in(msg)
308 .instrument(input_loop_span.clone())
309 .await;
310
311 emitter
313 .send(FixEventInternal::Created(session_id.clone()))
314 .await;
315
316 let input_timeout_duration = session.heartbeat_interval().mul_f32(TEST_REQUEST_THRESHOLD);
317 let input_stream = timeout_stream(input_timeout_duration, stream)
318 .map(|res| res.unwrap_or(InputEvent::Timeout));
319 pin_mut!(input_stream);
320
321 let output_stream = output_stream(session.clone(), session.heartbeat_interval(), receiver);
322 pin_mut!(output_stream);
323
324 let connection = Connection::new(session);
325 let (input_closed_tx, input_closed_rx) = oneshot::channel();
326
327 tokio::join!(
328 connection
329 .input_loop(
330 input_stream,
331 input_closed_tx,
332 force_disconnection_with_reason,
333 disconnect_rx,
334 )
335 .instrument(input_loop_span),
336 connection
337 .output_loop(writer, output_stream, input_closed_rx)
338 .instrument(output_loop_span),
339 );
340 session_span.in_scope(|| {
341 info!("connection closed");
342 });
343}
344
345pub(crate) async fn initiator_connection<S>(
346 tcp_stream: TcpStream,
347 settings: Settings,
348 session_settings: SessionSettings,
349 state: Rc<RefCell<State<S>>>,
350 active_sessions: Rc<RefCell<ActiveSessionsMap<S>>>,
351 emitter: Emitter,
352 pending_logout: PendingLogout,
353) where
354 S: MessagesStorage,
355{
356 let (source, sink) = tcp_stream.into_split();
357 state.borrow_mut().set_disconnected(false);
358 let session_id = session_settings.session_id.clone();
359
360 let (sender, receiver) = mpsc::unbounded_channel();
361 let sender = Sender::new(sender);
362
363 let (disconnect_tx, disconnect_rx) = oneshot::channel();
364
365 register_sender(session_id.clone(), sender.clone());
366
367 let _cleanup_guard = SessionCleanupGuard {
368 session_id: session_id.clone(),
369 state: state.clone(),
370 active_sessions: active_sessions.clone(),
371 pending_logout,
372 reset_on_disconnect: session_settings.reset_on_disconnect,
373 };
374
375 let session = Rc::new(Session::new(
376 settings,
377 session_settings,
378 state,
379 sender,
380 emitter.clone(),
381 disconnect_tx,
382 ));
383 active_sessions
384 .borrow_mut()
385 .insert(session_id.clone(), session.clone());
386
387 let session_span = info_span!(
388 "session",
389 id = %session_id
390 );
391
392 let input_loop_span = info_span!(parent: &session_span, "in");
393 let output_loop_span = info_span!(parent: &session_span, "out");
394
395 emitter
397 .send(FixEventInternal::Created(session_id.clone()))
398 .await;
399
400 let input_timeout_duration = session.heartbeat_interval().mul_f32(TEST_REQUEST_THRESHOLD);
401 let input_stream = timeout_stream(input_timeout_duration, input_stream(source))
402 .map(|res| res.unwrap_or(InputEvent::Timeout));
403 pin_mut!(input_stream);
404
405 let output_stream = output_stream(session.clone(), session.heartbeat_interval(), receiver);
406 pin_mut!(output_stream);
407
408 session.send_logon_request(&mut session.state().borrow_mut());
411
412 let connection = Connection::new(session);
413 let (input_closed_tx, input_closed_rx) = oneshot::channel();
414
415 tokio::join!(
416 connection
417 .input_loop(input_stream, input_closed_tx, None, disconnect_rx)
418 .instrument(input_loop_span),
419 connection
420 .output_loop(sink, output_stream, input_closed_rx)
421 .instrument(output_loop_span),
422 );
423 info!("connection closed");
424}
425
426impl<S: MessagesStorage> Connection<S> {
427 fn new(session: Rc<Session<S>>) -> Connection<S> {
428 Connection { session }
429 }
430
431 async fn input_loop(
432 &self,
433 mut input_stream: impl Stream<Item = InputEvent> + Unpin,
434 input_closed_tx: oneshot::Sender<()>,
435 force_disconnection_with_reason: Option<DisconnectReason>,
436 mut disconnect_rx: oneshot::Receiver<()>,
437 ) {
438 if let Some(disconnect_reason) = force_disconnection_with_reason {
439 self.session
440 .disconnect(&mut self.session.state().borrow_mut(), disconnect_reason);
441
442 input_closed_tx
446 .send(())
447 .expect("Failed to notify about closed inpuot");
448
449 return;
450 }
451
452 let mut disconnect_reason = DisconnectReason::Disconnected;
453 let mut logout_deadline = None;
454
455 let mut next_item = async || {
456 if logout_deadline.is_none() {
457 logout_deadline = self.session.logout_deadline();
458 }
459 if let Some(logout_deadline) = logout_deadline {
460 timeout_at(logout_deadline, input_stream.next())
461 .await
462 .unwrap_or(Some(InputEvent::LogoutTimeout))
463 } else {
464 input_stream.next().await
465 }
466 };
467
468 loop {
469 let event = tokio::select! {
470 event = next_item() => {
472 if let Some(event) = event {
473 event
475 } else {
476 break
477 }
478 }
479
480 _ = &mut disconnect_rx => {
482 info!("Disconnect signaled, exiting input loop");
483 disconnect_reason = DisconnectReason::ApplicationForcedDisconnect;
484 break;
485 }
486 };
487
488 if self.session.state().borrow().disconnected() {
490 info!("session disconnected, exit input processing");
491 input_closed_tx
495 .send(())
496 .expect("Failed to notify about closed input");
497 return;
498 }
499
500 match event {
501 InputEvent::Message(msg) => {
502 if let Some(reason) = self.session.on_message_in(msg).await {
503 info!(?reason, "disconnect, exit input processing");
504 disconnect_reason = reason;
505 break;
506 }
507 }
508 InputEvent::DeserializeError(error) => {
509 if let Some(reason) = self.session.on_deserialize_error(error).await {
510 info!(?reason, "disconnect, exit input processing");
511 disconnect_reason = reason;
512 break;
513 }
514 }
515 InputEvent::IoError(error) => {
516 error!(%error, "Input error");
517 disconnect_reason = DisconnectReason::IoError;
518 break;
519 }
520 InputEvent::Timeout => {
521 if self.session.on_in_timeout().await {
522 self.session.send_logout(
523 &mut self.session.state().borrow_mut(),
524 Some(SessionStatus::SessionLogoutComplete),
525 Some(FixString::from_ascii_lossy(
526 b"Grace period is over".to_vec(),
527 )),
528 );
529 break;
530 }
531 }
532 InputEvent::LogoutTimeout => {
533 info!("Logout timeout");
534 disconnect_reason = DisconnectReason::LogoutTimeout;
535 break;
536 }
537 }
538 }
539 self.session
540 .disconnect(&mut self.session.state().borrow_mut(), disconnect_reason);
541
542 input_closed_tx
546 .send(())
547 .expect("Failed to notify about closed inpout");
548 }
549
550 async fn output_loop(
551 &self,
552 mut sink: impl AsyncWrite + Unpin,
553 mut output_stream: impl Stream<Item = OutputEvent> + Unpin,
554 input_closed_rx: oneshot::Receiver<()>,
555 ) {
556 let mut sink_closed = false;
557 let mut disconnect_reason = DisconnectReason::Disconnected;
558 while let Some(event) = output_stream.next().await {
559 match event {
560 OutputEvent::Message(msg) => {
561 if sink_closed {
562 info!("Client disconnected, message will be stored for further resend");
567 } else if let Err(error) = sink.write_all(&msg).await {
568 sink_closed = true;
569 error!(%error, "Output write error");
570 }
582 }
583 OutputEvent::Timeout => self.session.on_out_timeout().await,
584 OutputEvent::Disconnect(reason) => {
585 info!("Client disconnected");
589 if !sink_closed && let Err(error) = sink.flush().await {
590 error!(%error, "final flush failed");
591 }
592 disconnect_reason = reason;
593 }
594 }
595 }
596 self.session.emit_logout(disconnect_reason).await;
600
601 let _ = input_closed_rx.await;
605 if let Err(error) = sink.shutdown().await {
606 error!(%error, "connection shutdown failed")
607 }
608 info!("disconnect, exit output processing");
609 }
610}