1use std::{
2 sync::{atomic::Ordering, Arc},
3 time::{Duration, Instant},
4};
5
6use futures::{future::BoxFuture, stream::BoxStream, FutureExt, Stream, StreamExt, TryStreamExt};
7use tracing::warn;
8
9use crate::{
10 retry::{ExponentialBackoff, SessionRetryPolicy},
11 session::{session_error, session_warn},
12 transport::{Connector, SecureChannelEventLoop, Transport, TransportPollResult},
13};
14use opcua_types::{
15 AttributeId, Error, QualifiedName, ReadValueId, StatusCode, TimestampsToReturn, VariableId,
16};
17
18use super::{
19 connect::{SessionConnectMode, SessionConnector},
20 services::subscriptions::event_loop::{SubscriptionActivity, SubscriptionEventLoop},
21 Session, SessionState,
22};
23
24#[derive(Debug)]
28#[non_exhaustive]
29pub enum SessionPollResult {
30 Transport(TransportPollResult),
32 ConnectionLost(StatusCode),
34 ReconnectFailed(StatusCode),
36 Reconnected(SessionConnectMode),
38 SessionActivity(SessionActivity),
40 Subscription(SubscriptionActivity),
42 BeginConnect,
44 FinishedDisconnect,
46}
47
48struct ConnectedState<T: Transport + Send + Sync + 'static> {
49 channel: SecureChannelEventLoop<T>,
50 keep_alive: BoxStream<'static, SessionActivity>,
51 subscriptions: BoxStream<'static, SubscriptionActivity>,
52 current_failed_keep_alive_count: u64,
53 currently_closing: bool,
54 disconnect_fut: BoxFuture<'static, Result<(), Error>>,
55}
56
57#[allow(clippy::large_enum_variant)]
60enum SessionEventLoopState<T: Transport + Send + Sync + 'static> {
61 Connected(ConnectedState<T>),
62 Connecting(SessionConnector, ExponentialBackoff, Instant),
63 Disconnected,
64}
65
66#[must_use = "The session event loop must be started for the session to work"]
68pub struct SessionEventLoop<T: Connector + Send + Sync + 'static> {
69 inner: Arc<Session>,
70 trigger_publish_recv: tokio::sync::watch::Receiver<Instant>,
71 close_rx: tokio::sync::watch::Receiver<bool>,
72 retry: SessionRetryPolicy,
73 keep_alive_interval: Duration,
74 max_failed_keep_alive_count: u64,
75 connector: T,
76}
77
78impl<T: Connector + Send + Sync + 'static> SessionEventLoop<T> {
79 pub(crate) fn new(
80 inner: Arc<Session>,
81 retry: SessionRetryPolicy,
82 trigger_publish_recv: tokio::sync::watch::Receiver<Instant>,
83 close_rx: tokio::sync::watch::Receiver<bool>,
84 keep_alive_interval: Duration,
85 max_failed_keep_alive_count: u64,
86 connector: T,
87 ) -> Self {
88 Self {
89 inner,
90 retry,
91 trigger_publish_recv,
92 close_rx,
93 keep_alive_interval,
94 max_failed_keep_alive_count,
95 connector,
96 }
97 }
98
99 pub async fn run(self) -> StatusCode {
107 let stream = self.enter();
108 tokio::pin!(stream);
109 loop {
110 let r = stream.try_next().await;
111
112 match r {
113 Ok(None) => break StatusCode::Good,
114 Err(e) => break e,
115 _ => (),
116 }
117 }
118 }
119
120 pub fn spawn(self) -> tokio::task::JoinHandle<StatusCode> {
128 tokio::task::spawn(self.run())
129 }
130
131 pub fn enter(self) -> impl Stream<Item = Result<SessionPollResult, StatusCode>> {
138 futures::stream::try_unfold(
139 (self, SessionEventLoopState::Disconnected),
140 |(mut slf, state)| async move {
141 if *slf.close_rx.borrow_and_update() {
143 return Ok(None);
144 }
145
146 let (res, state) = match state {
147 SessionEventLoopState::Connected(mut state) => {
148 tokio::select! {
149 r = state.channel.poll() => {
150 if let TransportPollResult::Closed(code) = r {
151 session_warn!(slf.inner, "Transport disconnected: {code}");
152 let _ = slf.inner.state_watch_tx.send(SessionState::Disconnected);
153
154 let should_reconnect = slf.inner.should_reconnect.load(Ordering::Relaxed);
155 if !should_reconnect {
156 return Ok(None);
157 }
158
159 Ok((
160 SessionPollResult::ConnectionLost(code),
161 SessionEventLoopState::Disconnected,
162 ))
163 } else {
164 Ok((
165 SessionPollResult::Transport(r),
166 SessionEventLoopState::Connected(state),
167 ))
168 }
169 }
170 r = state.keep_alive.next() => {
171 let Some(r) = r else {
173 session_error!(slf.inner, "Session activity loop ended unexpectedly");
174 return Err(StatusCode::BadUnexpectedError);
175 };
176
177 match r {
178 SessionActivity::KeepAliveSucceeded => state.current_failed_keep_alive_count = 0,
179 SessionActivity::KeepAliveFailed(status_code) => {
180 session_warn!(slf.inner, "Keep alive failed: {status_code}");
181 state.current_failed_keep_alive_count += 1;
182 if !state.currently_closing
183 && state.current_failed_keep_alive_count >= slf.max_failed_keep_alive_count
184 && slf.max_failed_keep_alive_count != 0
185 {
186 session_error!(slf.inner, "Maximum number of failed keep-alives exceed limit, session will be closed.");
187 state.currently_closing = true;
188 let s = slf.inner.clone();
189 state.disconnect_fut = async move {
190 s.disconnect_inner(false, false).await
191 }.boxed();
192 }
193 },
194 }
195
196 Ok((
197 SessionPollResult::SessionActivity(r),
198 SessionEventLoopState::Connected(state),
199 ))
200 }
201 r = state.subscriptions.next() => {
202 let Some(r) = r else {
204 session_error!(slf.inner, "Subscription event loop ended unexpectedly");
205 return Err(StatusCode::BadUnexpectedError);
206 };
207
208 if let SubscriptionActivity::FatalFailure(e) = &r {
209 if !state.currently_closing {
210 session_error!(slf.inner, "Fatal error from subscription loop ({e}), session will be closed.");
211 state.currently_closing = true;
212 let s = slf.inner.clone();
213 state.disconnect_fut = async move {
214 s.disconnect_inner(false, false).await
215 }.boxed();
216 }
217 }
218
219 Ok((
220 SessionPollResult::Subscription(r),
221 SessionEventLoopState::Connected(state),
222 ))
223 }
224 _ = &mut state.disconnect_fut => {
225 Ok((
228 SessionPollResult::FinishedDisconnect,
229 SessionEventLoopState::Connected(state)
230 ))
231 }
232 _ = slf.close_rx.changed(), if !state.currently_closing => {
233 if *slf.close_rx.borrow_and_update() {
234 state.currently_closing = true;
235 let s = slf.inner.clone();
236 state.disconnect_fut = async move {
237 s.disconnect_inner(true, false).await
238 }.boxed();
239 }
240 Ok((
241 SessionPollResult::FinishedDisconnect,
242 SessionEventLoopState::Connected(state),
243 ))
244 }
245 }
246 }
247 SessionEventLoopState::Disconnected => {
248 let connector = SessionConnector::new(slf.inner.clone());
249
250 let _ = slf.inner.state_watch_tx.send(SessionState::Connecting);
251
252 Ok((
253 SessionPollResult::BeginConnect,
254 SessionEventLoopState::Connecting(
255 connector,
256 slf.retry.new_backoff(),
257 Instant::now(),
258 ),
259 ))
260 }
261 SessionEventLoopState::Connecting(connector, mut backoff, next_try) => {
262 tokio::time::sleep_until(next_try.into()).await;
263
264 match connector.try_connect(&slf.connector).await {
265 Ok((channel, result)) => {
266 let _ = slf.inner.state_watch_tx.send(SessionState::Connected);
267 Ok((
268 SessionPollResult::Reconnected(result),
269 SessionEventLoopState::Connected(ConnectedState {
270 channel,
271 keep_alive: SessionActivityLoop::new(
272 slf.inner.clone(),
273 slf.keep_alive_interval,
274 )
275 .run()
276 .boxed(),
277 subscriptions: SubscriptionEventLoop::new(
278 slf.inner.clone(),
279 slf.trigger_publish_recv.clone(),
280 )
281 .run()
282 .boxed(),
283 current_failed_keep_alive_count: 0,
284 currently_closing: false,
285 disconnect_fut: futures::future::pending().boxed(),
286 }),
287 ))
288 }
289 Err(e) => {
290 warn!("Failed to connect to server: {e}");
291 match backoff.next() {
292 Some(x) => Ok((
293 SessionPollResult::ReconnectFailed(e.status()),
294 SessionEventLoopState::Connecting(
295 connector,
296 backoff,
297 Instant::now() + x,
298 ),
299 )),
300 None => Err(e),
301 }
302 }
303 }
304 }
305 }?;
306
307 Ok(Some((res, (slf, state))))
308 },
309 )
310 }
311}
312
313#[derive(Debug, Clone)]
315pub enum SessionActivity {
316 KeepAliveSucceeded,
318 KeepAliveFailed(StatusCode),
320}
321
322enum SessionTickEvent {
323 KeepAlive,
324}
325
326struct SessionIntervals {
327 keep_alive: tokio::time::Interval,
328}
329
330impl SessionIntervals {
331 fn new(keep_alive_interval: Duration) -> Self {
332 let mut keep_alive = tokio::time::interval(keep_alive_interval);
333 keep_alive.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
334
335 Self { keep_alive }
336 }
337
338 async fn next(&mut self) -> SessionTickEvent {
339 tokio::select! {
340 _ = self.keep_alive.tick() => SessionTickEvent::KeepAlive
341 }
342 }
343}
344
345struct SessionActivityLoop {
346 inner: Arc<Session>,
347 tick_gen: SessionIntervals,
348}
349
350impl SessionActivityLoop {
351 fn new(inner: Arc<Session>, keep_alive_interval: Duration) -> Self {
352 Self {
353 inner,
354 tick_gen: SessionIntervals::new(keep_alive_interval),
355 }
356 }
357
358 fn run(self) -> impl Stream<Item = SessionActivity> {
359 futures::stream::unfold(self, |mut slf| async move {
360 match slf.tick_gen.next().await {
361 SessionTickEvent::KeepAlive => {
362 let now = Instant::now();
363 let res = slf
364 .inner
365 .read(
366 &[ReadValueId {
367 node_id: VariableId::Server_ServerStatus_State.into(),
368 attribute_id: AttributeId::Value as u32,
369 index_range: Default::default(),
370 data_encoding: QualifiedName::null(),
371 }],
372 TimestampsToReturn::Server,
373 1f64,
374 )
375 .await;
376 let elapsed = now.elapsed();
377
378 let data_value = match res.map(|r| r.into_iter().next()) {
379 Ok(Some(data_value)) => {
380 slf.inner
383 .publish_limits_watch_tx
384 .send_modify(|limits| limits.update_message_roundtrip(elapsed));
385 data_value
386 }
387 Ok(None) => {
390 return Some((
391 SessionActivity::KeepAliveFailed(StatusCode::BadUnknownResponse),
392 slf,
393 ))
394 }
395 Err(e) => return Some((SessionActivity::KeepAliveFailed(e.status()), slf)),
396 };
397
398 match data_value.value.and_then(|v| v.try_cast_to().ok()) {
399 Some(0) => Some((SessionActivity::KeepAliveSucceeded, slf)),
400 Some(s) => {
401 warn!("Keep alive failed, non-running status code {s}");
402 Some((
403 SessionActivity::KeepAliveFailed(StatusCode::BadServerHalted),
404 slf,
405 ))
406 }
407 None => Some((
408 SessionActivity::KeepAliveFailed(StatusCode::BadUnknownResponse),
409 slf,
410 )),
411 }
412 }
413 }
414 })
415 }
416}