1use std::{
9 io,
10 net::{TcpStream, UdpSocket},
11 sync::MutexGuard,
12 time::{Duration, Instant},
13};
14
15use crate::Error;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19#[non_exhaustive]
20pub enum CommandCategory {
21 Quick,
23 Movement,
25 Preset,
27 LongRunning,
29 Network,
31 Custom,
33}
34
35impl CommandCategory {
36 #[must_use]
38 pub const fn default_timeout(&self) -> Duration {
39 match self {
40 Self::Quick => Duration::from_secs(5), Self::Movement => Duration::from_secs(30), Self::Preset => Duration::from_secs(60), Self::LongRunning => Duration::from_secs(300), Self::Network => Duration::from_secs(5), Self::Custom => Duration::from_secs(60), }
47 }
48}
49
50#[derive(Debug, Copy, Clone)]
52#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
53#[cfg_attr(feature = "serde", serde_with::serde_as)]
54#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
55pub struct TimeoutConfig {
56 #[cfg_attr(
58 feature = "serde",
59 serde_as(as = "serde_with::DurationMilliSeconds<u64>")
60 )]
61 #[cfg_attr(
62 feature = "schemars",
63 schemars(with = "u64", description = "Timeout in milliseconds")
64 )]
65 pub ack_timeout: Duration,
66 #[cfg_attr(
68 feature = "serde",
69 serde_as(as = "serde_with::DurationMilliSeconds<u64>")
70 )]
71 #[cfg_attr(
72 feature = "schemars",
73 schemars(with = "u64", description = "Timeout in milliseconds")
74 )]
75 pub quick_timeout: Duration,
76 #[cfg_attr(
78 feature = "serde",
79 serde_as(as = "serde_with::DurationMilliSeconds<u64>")
80 )]
81 #[cfg_attr(
82 feature = "schemars",
83 schemars(with = "u64", description = "Timeout in milliseconds")
84 )]
85 pub movement_timeout: Duration,
86 #[cfg_attr(
88 feature = "serde",
89 serde_as(as = "serde_with::DurationMilliSeconds<u64>")
90 )]
91 #[cfg_attr(
92 feature = "schemars",
93 schemars(with = "u64", description = "Timeout in milliseconds")
94 )]
95 pub preset_timeout: Duration,
96 #[cfg_attr(
98 feature = "serde",
99 serde_as(as = "serde_with::DurationMilliSeconds<u64>")
100 )]
101 #[cfg_attr(
102 feature = "schemars",
103 schemars(with = "u64", description = "Timeout in milliseconds")
104 )]
105 pub long_timeout: Duration,
106 #[cfg_attr(
108 feature = "serde",
109 serde_as(as = "serde_with::DurationMilliSeconds<u64>")
110 )]
111 #[cfg_attr(
112 feature = "schemars",
113 schemars(with = "u64", description = "Timeout in milliseconds")
114 )]
115 pub network_timeout: Duration,
116 #[cfg_attr(
118 feature = "serde",
119 serde_as(as = "serde_with::DurationMilliSeconds<u64>")
120 )]
121 #[cfg_attr(
122 feature = "schemars",
123 schemars(with = "u64", description = "Timeout in milliseconds")
124 )]
125 pub default_timeout: Duration,
126}
127
128impl Default for TimeoutConfig {
129 fn default() -> Self {
130 Self {
131 ack_timeout: Duration::from_millis(500), quick_timeout: Duration::from_secs(5), movement_timeout: Duration::from_secs(30), preset_timeout: Duration::from_secs(60), long_timeout: Duration::from_secs(300), network_timeout: Duration::from_secs(5), default_timeout: Duration::from_secs(60), }
139 }
140}
141
142impl TimeoutConfig {
143 #[must_use]
145 pub const fn uniform(timeout: Duration) -> Self {
146 Self {
147 ack_timeout: timeout,
148 quick_timeout: timeout,
149 movement_timeout: timeout,
150 preset_timeout: timeout,
151 long_timeout: timeout,
152 network_timeout: timeout,
153 default_timeout: timeout,
154 }
155 }
156
157 #[must_use]
159 pub const fn get_timeout(&self, category: CommandCategory) -> Duration {
160 match category {
161 CommandCategory::Quick => self.quick_timeout,
162 CommandCategory::Movement => self.movement_timeout,
163 CommandCategory::Preset => self.preset_timeout,
164 CommandCategory::LongRunning => self.long_timeout,
165 CommandCategory::Network => self.network_timeout,
166 CommandCategory::Custom => self.default_timeout,
167 }
168 }
169
170 #[must_use]
172 pub fn builder() -> TimeoutConfigBuilder {
173 TimeoutConfigBuilder::default()
174 }
175}
176
177#[derive(Debug, Clone, Copy, Default)]
179pub struct TimeoutConfigBuilder {
180 config: TimeoutConfig,
181}
182
183impl TimeoutConfigBuilder {
184 #[allow(clippy::missing_const_for_fn)]
186 #[must_use]
187 pub fn ack_timeout(mut self, timeout: Duration) -> Self {
188 self.config.ack_timeout = timeout;
189 self
190 }
191
192 #[allow(clippy::missing_const_for_fn)]
194 #[must_use]
195 pub fn quick_timeout(mut self, timeout: Duration) -> Self {
196 self.config.quick_timeout = timeout;
197 self
198 }
199
200 #[allow(clippy::missing_const_for_fn)]
202 #[must_use]
203 pub fn movement_timeout(mut self, timeout: Duration) -> Self {
204 self.config.movement_timeout = timeout;
205 self
206 }
207
208 #[allow(clippy::missing_const_for_fn)]
210 #[must_use]
211 pub fn preset_timeout(mut self, timeout: Duration) -> Self {
212 self.config.preset_timeout = timeout;
213 self
214 }
215
216 #[allow(clippy::missing_const_for_fn)]
218 #[must_use]
219 pub fn long_timeout(mut self, timeout: Duration) -> Self {
220 self.config.long_timeout = timeout;
221 self
222 }
223
224 #[allow(clippy::missing_const_for_fn)]
226 #[must_use]
227 pub fn network_timeout(mut self, timeout: Duration) -> Self {
228 self.config.network_timeout = timeout;
229 self
230 }
231
232 #[allow(clippy::missing_const_for_fn)]
234 #[must_use]
235 pub fn default_timeout(mut self, timeout: Duration) -> Self {
236 self.config.default_timeout = timeout;
237 self
238 }
239
240 #[must_use]
242 pub const fn build(self) -> TimeoutConfig {
243 self.config
244 }
245}
246
247#[cfg(test)]
248#[allow(clippy::expect_used)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn test_command_category_defaults() {
254 assert_eq!(
255 CommandCategory::Quick.default_timeout(),
256 Duration::from_secs(5)
257 );
258 assert_eq!(
259 CommandCategory::Movement.default_timeout(),
260 Duration::from_secs(30)
261 );
262 assert_eq!(
263 CommandCategory::Preset.default_timeout(),
264 Duration::from_secs(60)
265 );
266 assert_eq!(
267 CommandCategory::LongRunning.default_timeout(),
268 Duration::from_secs(300)
269 );
270 assert_eq!(
271 CommandCategory::Network.default_timeout(),
272 Duration::from_secs(5)
273 );
274 assert_eq!(
275 CommandCategory::Custom.default_timeout(),
276 Duration::from_secs(60)
277 );
278 }
279
280 #[test]
281 fn test_timeout_config_default() {
282 let config = TimeoutConfig::default();
283 assert_eq!(config.ack_timeout, Duration::from_millis(500));
284 assert_eq!(config.quick_timeout, Duration::from_secs(5));
285 assert_eq!(config.movement_timeout, Duration::from_secs(30));
286 assert_eq!(config.preset_timeout, Duration::from_secs(60));
287 assert_eq!(config.long_timeout, Duration::from_secs(300));
288 assert_eq!(config.network_timeout, Duration::from_secs(5));
289 assert_eq!(config.default_timeout, Duration::from_secs(60));
290 }
291
292 #[test]
293 fn test_timeout_config_uniform() {
294 let timeout = Duration::from_secs(5);
295 let config = TimeoutConfig::uniform(timeout);
296 assert_eq!(config.ack_timeout, timeout);
297 assert_eq!(config.quick_timeout, timeout);
298 assert_eq!(config.movement_timeout, timeout);
299 assert_eq!(config.preset_timeout, timeout);
300 assert_eq!(config.long_timeout, timeout);
301 assert_eq!(config.network_timeout, timeout);
302 assert_eq!(config.default_timeout, timeout);
303 }
304
305 #[test]
306 fn test_get_timeout() {
307 let config = TimeoutConfig::default();
308 assert_eq!(
309 config.get_timeout(CommandCategory::Quick),
310 Duration::from_secs(5)
311 );
312 assert_eq!(
313 config.get_timeout(CommandCategory::Movement),
314 Duration::from_secs(30)
315 );
316 assert_eq!(
317 config.get_timeout(CommandCategory::Preset),
318 Duration::from_secs(60)
319 );
320 assert_eq!(
321 config.get_timeout(CommandCategory::LongRunning),
322 Duration::from_secs(300)
323 );
324 assert_eq!(
325 config.get_timeout(CommandCategory::Network),
326 Duration::from_secs(5)
327 );
328 assert_eq!(
329 config.get_timeout(CommandCategory::Custom),
330 Duration::from_secs(60)
331 );
332 }
333
334 #[test]
335 fn test_timeout_config_builder() {
336 let config = TimeoutConfig::builder()
337 .quick_timeout(Duration::from_secs(1))
338 .movement_timeout(Duration::from_secs(5))
339 .preset_timeout(Duration::from_secs(30))
340 .long_timeout(Duration::from_secs(120))
341 .default_timeout(Duration::from_secs(15))
342 .build();
343
344 assert_eq!(config.quick_timeout, Duration::from_secs(1));
345 assert_eq!(config.movement_timeout, Duration::from_secs(5));
346 assert_eq!(config.preset_timeout, Duration::from_secs(30));
347 assert_eq!(config.long_timeout, Duration::from_secs(120));
348 assert_eq!(config.default_timeout, Duration::from_secs(15));
349 }
350}
351
352pub trait TimeoutManager {
357 fn with_timeout<F, R>(&mut self, timeout: Duration, f: F) -> Result<R, Error>
377 where
378 F: FnOnce(&mut Self) -> Result<R, Error>;
379
380 fn get_read_timeout(&self) -> io::Result<Option<Duration>>;
382
383 fn set_read_timeout(&mut self, timeout: Option<Duration>) -> io::Result<()>;
385
386 fn get_write_timeout(&self) -> io::Result<Option<Duration>>;
388
389 fn set_write_timeout(&mut self, timeout: Option<Duration>) -> io::Result<()>;
391
392 fn with_read_timeout<F, R>(&mut self, timeout: Duration, f: F) -> Result<R, Error>
396 where
397 F: FnOnce(&mut Self) -> Result<R, Error>,
398 {
399 let original_timeout = self.get_read_timeout()?;
400 self.set_read_timeout(Some(timeout))?;
401 let result = f(self);
402 self.set_read_timeout(original_timeout)?;
403
404 result
405 }
406
407 fn with_write_timeout<F, R>(&mut self, timeout: Duration, f: F) -> Result<R, Error>
411 where
412 F: FnOnce(&mut Self) -> Result<R, Error>,
413 {
414 let original_timeout = self.get_write_timeout()?;
415 self.set_write_timeout(Some(timeout))?;
416 let result = f(self);
417 self.set_write_timeout(original_timeout)?;
418
419 result
420 }
421}
422
423impl TimeoutManager for TcpStream {
425 fn with_timeout<F, R>(&mut self, timeout: Duration, f: F) -> Result<R, Error>
426 where
427 F: FnOnce(&mut Self) -> Result<R, Error>,
428 {
429 self.with_read_timeout(timeout, f)
430 }
431
432 fn get_read_timeout(&self) -> io::Result<Option<Duration>> {
433 self.read_timeout()
434 }
435
436 fn set_read_timeout(&mut self, timeout: Option<Duration>) -> io::Result<()> {
437 TcpStream::set_read_timeout(self, timeout)
438 }
439
440 fn get_write_timeout(&self) -> io::Result<Option<Duration>> {
441 self.write_timeout()
442 }
443
444 fn set_write_timeout(&mut self, timeout: Option<Duration>) -> io::Result<()> {
445 TcpStream::set_write_timeout(self, timeout)
446 }
447}
448
449impl TimeoutManager for UdpSocket {
451 fn with_timeout<F, R>(&mut self, timeout: Duration, f: F) -> Result<R, Error>
452 where
453 F: FnOnce(&mut Self) -> Result<R, Error>,
454 {
455 self.with_read_timeout(timeout, f)
456 }
457
458 fn get_read_timeout(&self) -> io::Result<Option<Duration>> {
459 self.read_timeout()
460 }
461
462 fn set_read_timeout(&mut self, timeout: Option<Duration>) -> io::Result<()> {
463 UdpSocket::set_read_timeout(self, timeout)
464 }
465
466 fn get_write_timeout(&self) -> io::Result<Option<Duration>> {
467 self.write_timeout()
468 }
469
470 fn set_write_timeout(&mut self, timeout: Option<Duration>) -> io::Result<()> {
471 UdpSocket::set_write_timeout(self, timeout)
472 }
473}
474
475pub struct TimeoutGuard<'a, T: TimeoutManager> {
480 socket: &'a mut T,
481 original_read_timeout: Option<Duration>,
482 original_write_timeout: Option<Duration>,
483 restored: bool,
484}
485
486impl<T: TimeoutManager> std::fmt::Debug for TimeoutGuard<'_, T> {
487 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
488 f.debug_struct("TimeoutGuard")
489 .field("original_read_timeout", &self.original_read_timeout)
490 .field("original_write_timeout", &self.original_write_timeout)
491 .field("restored", &self.restored)
492 .finish()
493 }
494}
495
496impl<'a, T: TimeoutManager> TimeoutGuard<'a, T> {
497 pub fn new(
499 socket: &'a mut T,
500 read_timeout: Option<Duration>,
501 write_timeout: Option<Duration>,
502 ) -> Result<Self, Error> {
503 let original_read_timeout = socket.get_read_timeout()?;
504 let original_write_timeout = socket.get_write_timeout()?;
505
506 if let Some(timeout) = read_timeout {
507 socket.set_read_timeout(Some(timeout))?;
508 }
509
510 if let Some(timeout) = write_timeout {
511 socket.set_write_timeout(Some(timeout))?;
512 }
513
514 Ok(Self {
515 socket,
516 original_read_timeout,
517 original_write_timeout,
518 restored: false,
519 })
520 }
521
522 pub fn restore(&mut self) -> Result<(), Error> {
524 if !self.restored {
525 self.socket.set_read_timeout(self.original_read_timeout)?;
526 self.socket.set_write_timeout(self.original_write_timeout)?;
527 self.restored = true;
528 }
529 Ok(())
530 }
531}
532
533impl<T: TimeoutManager> Drop for TimeoutGuard<'_, T> {
534 fn drop(&mut self) {
535 let _ = self.restore();
537 }
538}
539
540pub fn with_timeout_on_guard<T, F, R>(
545 guard: &mut MutexGuard<'_, T>,
546 timeout: Duration,
547 f: F,
548) -> Result<R, Error>
549where
550 T: TimeoutManager,
551 F: FnOnce(&mut T) -> Result<R, Error>,
552{
553 guard.with_read_timeout(timeout, f)
554}
555
556#[derive(Debug, Clone, Copy)]
569pub struct Deadline {
570 pub deadline: Instant,
572 pub timeout: Duration,
574}
575
576impl Deadline {
577 #[must_use]
584 pub fn from_timeout(timeout: Duration) -> Self {
585 Self::from_timeout_at(Instant::now(), timeout)
586 }
587
588 #[must_use]
600 pub fn from_timeout_at(now: Instant, timeout: Duration) -> Self {
601 let deadline = now + timeout;
602 Self { deadline, timeout }
603 }
604
605 #[must_use]
607 pub const fn from_instant(deadline: Instant, timeout: Duration) -> Self {
608 Self { deadline, timeout }
609 }
610
611 #[must_use]
618 pub fn is_expired(&self) -> bool {
619 self.is_expired_at(Instant::now())
620 }
621
622 #[must_use]
628 pub fn is_expired_at(&self, now: Instant) -> bool {
629 now > self.deadline
630 }
631
632 #[must_use]
639 pub fn remaining(&self) -> Duration {
640 self.remaining_at(Instant::now())
641 }
642
643 #[must_use]
649 pub fn remaining_at(&self, now: Instant) -> Duration {
650 self.deadline.saturating_duration_since(now)
651 }
652
653 #[must_use]
660 pub fn elapsed(&self) -> Duration {
661 self.elapsed_at(Instant::now())
662 }
663
664 #[must_use]
670 pub fn elapsed_at(&self, now: Instant) -> Duration {
671 self.timeout.saturating_sub(self.remaining_at(now))
672 }
673}
674
675pub trait CommandTimeout {
680 fn timeout_class(&self) -> CommandCategory;
685}
686
687impl<T> CommandTimeout for T
692where
693 T: crate::command::ViscaCommand,
694{
695 fn timeout_class(&self) -> CommandCategory {
696 T::TIMEOUT_CATEGORY
697 }
698}
699
700#[derive(Debug, Clone, Copy, Default)]
712pub struct TimeoutPolicy {
713 config: TimeoutConfig,
714}
715
716impl TimeoutPolicy {
717 #[must_use]
719 pub const fn new(config: TimeoutConfig) -> Self {
720 Self { config }
721 }
722
723 #[must_use]
725 pub const fn get_timeout(&self, class: CommandCategory) -> Duration {
726 self.config.get_timeout(class)
727 }
728
729 #[must_use]
736 pub fn deadline_for(&self, class: CommandCategory) -> Deadline {
737 Deadline::from_timeout(self.get_timeout(class))
738 }
739
740 #[must_use]
752 pub fn deadline_for_at(&self, now: Instant, class: CommandCategory) -> Deadline {
753 Deadline::from_timeout_at(now, self.get_timeout(class))
754 }
755
756 pub fn update_config(&mut self, config: TimeoutConfig) {
758 self.config = config;
759 }
760}
761
762#[cfg(test)]
763#[allow(clippy::expect_used)]
764mod timeout_manager_tests {
765 use std::{
766 io,
767 net::{TcpListener, TcpStream, UdpSocket},
768 sync::Mutex,
769 thread,
770 };
771
772 use super::*;
773
774 #[derive(Debug, Default)]
775 struct MockTimeoutSocket {
776 read_timeout: Option<Duration>,
777 write_timeout: Option<Duration>,
778 }
779
780 impl MockTimeoutSocket {
781 const fn new(read_timeout: Option<Duration>, write_timeout: Option<Duration>) -> Self {
782 Self {
783 read_timeout,
784 write_timeout,
785 }
786 }
787 }
788
789 impl TimeoutManager for MockTimeoutSocket {
790 fn with_timeout<F, R>(&mut self, timeout: Duration, f: F) -> Result<R, Error>
791 where
792 F: FnOnce(&mut Self) -> Result<R, Error>,
793 {
794 self.with_read_timeout(timeout, f)
795 }
796
797 fn get_read_timeout(&self) -> io::Result<Option<Duration>> {
798 Ok(self.read_timeout)
799 }
800
801 fn set_read_timeout(&mut self, timeout: Option<Duration>) -> io::Result<()> {
802 self.read_timeout = timeout;
803 Ok(())
804 }
805
806 fn get_write_timeout(&self) -> io::Result<Option<Duration>> {
807 Ok(self.write_timeout)
808 }
809
810 fn set_write_timeout(&mut self, timeout: Option<Duration>) -> io::Result<()> {
811 self.write_timeout = timeout;
812 Ok(())
813 }
814 }
815
816 #[test]
817 fn test_with_read_timeout_restores_original_timeout_on_success() {
818 let original_timeout = Some(Duration::from_secs(5));
819 let requested_timeout = Duration::from_secs(1);
820 let mut socket = MockTimeoutSocket::new(original_timeout, None);
821
822 let result = socket.with_read_timeout(requested_timeout, |socket| {
823 assert_eq!(socket.read_timeout, Some(requested_timeout));
824 Ok(42)
825 });
826
827 assert_eq!(result.expect("temporary read timeout should succeed"), 42);
828 assert_eq!(socket.read_timeout, original_timeout);
829 }
830
831 #[test]
832 fn test_with_read_timeout_restores_original_timeout_on_error() {
833 let original_timeout = Some(Duration::from_secs(5));
834 let requested_timeout = Duration::from_secs(1);
835 let mut socket = MockTimeoutSocket::new(original_timeout, None);
836
837 let error = socket
838 .with_read_timeout(requested_timeout, |socket| {
839 assert_eq!(socket.read_timeout, Some(requested_timeout));
840 Err::<(), Error>(Error::from(io::Error::other(
841 "expected timeout test failure",
842 )))
843 })
844 .expect_err("closure failure should be propagated");
845
846 assert!(matches!(error, Error::Io(_)));
847 assert_eq!(socket.read_timeout, original_timeout);
848 }
849
850 #[test]
851 fn test_with_write_timeout_restores_original_timeout() {
852 let original_timeout = Some(Duration::from_secs(10));
853 let requested_timeout = Duration::from_secs(2);
854 let mut socket = MockTimeoutSocket::new(None, original_timeout);
855
856 let result = socket.with_write_timeout(requested_timeout, |socket| {
857 assert_eq!(socket.write_timeout, Some(requested_timeout));
858 Ok("ok")
859 });
860
861 assert_eq!(
862 result.expect("temporary write timeout should succeed"),
863 "ok"
864 );
865 assert_eq!(socket.write_timeout, original_timeout);
866 }
867
868 #[test]
869 fn test_timeout_guard_restores_timeouts() {
870 let original_read_timeout = Some(Duration::from_secs(5));
871 let original_write_timeout = Some(Duration::from_secs(10));
872 let requested_read_timeout = Duration::from_secs(1);
873 let requested_write_timeout = Duration::from_secs(2);
874 let mut socket = MockTimeoutSocket::new(original_read_timeout, original_write_timeout);
875
876 {
877 let mut guard = TimeoutGuard::new(
878 &mut socket,
879 Some(requested_read_timeout),
880 Some(requested_write_timeout),
881 )
882 .expect("Failed to create timeout guard");
883
884 assert_eq!(guard.socket.read_timeout, Some(requested_read_timeout));
885 assert_eq!(guard.socket.write_timeout, Some(requested_write_timeout));
886
887 guard
888 .restore()
889 .expect("Failed to restore original timeouts");
890 assert_eq!(guard.socket.read_timeout, original_read_timeout);
891 assert_eq!(guard.socket.write_timeout, original_write_timeout);
892
893 guard
894 .restore()
895 .expect("timeout restoration should be idempotent");
896 }
897
898 assert_eq!(socket.read_timeout, original_read_timeout);
899 assert_eq!(socket.write_timeout, original_write_timeout);
900 }
901
902 #[test]
903 fn test_with_timeout_on_guard_restores_read_timeout() {
904 let original_timeout = Some(Duration::from_secs(5));
905 let requested_timeout = Duration::from_secs(3);
906 let socket = Mutex::new(MockTimeoutSocket::new(original_timeout, None));
907 let mut guard = socket.lock().expect("mutex should not be poisoned");
908
909 let result = with_timeout_on_guard(&mut guard, requested_timeout, |socket| {
910 assert_eq!(socket.read_timeout, Some(requested_timeout));
911 Ok("ok")
912 });
913
914 assert_eq!(
915 result.expect("temporary timeout through mutex guard should succeed"),
916 "ok"
917 );
918 assert_eq!(guard.read_timeout, original_timeout);
919 }
920
921 #[test]
922 #[cfg_attr(miri, ignore = "requires real TCP sockets")]
923 fn test_tcp_timeout_manager_smoke() {
924 let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind test listener");
926 let addr = listener
927 .local_addr()
928 .expect("Failed to get listener address");
929
930 thread::spawn(move || {
932 let (_stream, _) = listener.accept().expect("Failed to accept connection");
933 thread::sleep(Duration::from_secs(10));
935 });
936
937 let mut stream = TcpStream::connect(addr).expect("Failed to connect to test server");
939
940 assert_eq!(
942 stream.get_read_timeout().expect("Failed to get timeout"),
943 None
944 );
945
946 stream
947 .set_read_timeout(Some(Duration::from_secs(5)))
948 .expect("Failed to set timeout");
949 assert_eq!(
950 stream.get_read_timeout().expect("Failed to get timeout"),
951 Some(Duration::from_secs(5))
952 );
953
954 let result = stream.with_read_timeout(Duration::from_secs(1), |s| {
956 assert_eq!(
958 s.get_read_timeout()
959 .expect("Failed to get timeout in guard"),
960 Some(Duration::from_secs(1))
961 );
962 Ok(42)
963 });
964
965 assert_eq!(result.expect("Test operation failed"), 42);
966 assert_eq!(
968 stream
969 .get_read_timeout()
970 .expect("Failed to get timeout after guard"),
971 Some(Duration::from_secs(5))
972 );
973 }
974
975 #[test]
976 #[cfg_attr(miri, ignore = "requires real UDP sockets")]
977 fn test_udp_timeout_manager_smoke() {
978 let mut socket = UdpSocket::bind("127.0.0.1:0").expect("Failed to bind UDP socket");
979
980 assert_eq!(
982 socket
983 .get_read_timeout()
984 .expect("Failed to get UDP timeout"),
985 None
986 );
987
988 socket
989 .set_read_timeout(Some(Duration::from_secs(3)))
990 .expect("Failed to set UDP timeout");
991 assert_eq!(
992 socket
993 .get_read_timeout()
994 .expect("Failed to get UDP timeout"),
995 Some(Duration::from_secs(3))
996 );
997
998 let result = socket.with_read_timeout(Duration::from_secs(2), |s| {
1000 assert_eq!(
1002 s.get_read_timeout()
1003 .expect("Failed to get UDP timeout in guard"),
1004 Some(Duration::from_secs(2))
1005 );
1006 Ok("success")
1007 });
1008
1009 assert_eq!(result.expect("UDP test operation failed"), "success");
1010 assert_eq!(
1012 socket
1013 .get_read_timeout()
1014 .expect("Failed to get UDP timeout after guard"),
1015 Some(Duration::from_secs(3))
1016 );
1017 }
1018
1019 #[test]
1020 fn test_timeout_guard_restores_timeouts_on_drop() {
1021 let original_read_timeout = Some(Duration::from_secs(5));
1022 let original_write_timeout = Some(Duration::from_secs(10));
1023 let mut socket = MockTimeoutSocket::new(original_read_timeout, original_write_timeout);
1024
1025 {
1026 let guard = TimeoutGuard::new(
1027 &mut socket,
1028 Some(Duration::from_secs(1)),
1029 Some(Duration::from_secs(2)),
1030 )
1031 .expect("Failed to create timeout guard");
1032
1033 assert_eq!(guard.socket.read_timeout, Some(Duration::from_secs(1)));
1034 assert_eq!(guard.socket.write_timeout, Some(Duration::from_secs(2)));
1035 }
1036
1037 assert_eq!(socket.read_timeout, original_read_timeout);
1038 assert_eq!(socket.write_timeout, original_write_timeout);
1039 }
1040
1041 #[test]
1042 fn test_deadline() {
1043 let timeout = Duration::from_millis(100);
1044 let now = Instant::now();
1045 let deadline = Deadline::from_timeout_at(now, timeout);
1046
1047 assert!(!deadline.is_expired_at(now));
1048 assert_eq!(deadline.timeout, timeout);
1049 assert_eq!(deadline.remaining_at(now), timeout);
1050 assert_eq!(deadline.elapsed_at(now), Duration::ZERO);
1051
1052 let during_timeout = now + Duration::from_millis(10);
1053 assert!(!deadline.is_expired_at(during_timeout));
1054 assert_eq!(
1055 deadline.remaining_at(during_timeout),
1056 Duration::from_millis(90)
1057 );
1058 assert_eq!(
1059 deadline.elapsed_at(during_timeout),
1060 Duration::from_millis(10)
1061 );
1062
1063 let at_deadline = now + timeout;
1064 assert!(!deadline.is_expired_at(at_deadline));
1065 assert_eq!(deadline.remaining_at(at_deadline), Duration::ZERO);
1066
1067 let after_deadline = at_deadline + Duration::from_nanos(1);
1068 assert!(deadline.is_expired_at(after_deadline));
1069 assert_eq!(deadline.remaining_at(after_deadline), Duration::ZERO);
1070 }
1071
1072 #[test]
1073 fn test_timeout_policy() {
1074 let policy = TimeoutPolicy::default();
1075
1076 assert_eq!(
1077 policy.get_timeout(CommandCategory::Quick),
1078 Duration::from_secs(5)
1079 );
1080 assert_eq!(
1081 policy.get_timeout(CommandCategory::Movement),
1082 Duration::from_secs(30)
1083 );
1084
1085 let deadline = policy.deadline_for(CommandCategory::Quick);
1086 assert!(!deadline.is_expired());
1087 assert_eq!(deadline.timeout, Duration::from_secs(5));
1088
1089 let mut policy = TimeoutPolicy::default();
1091 let new_config = TimeoutConfig::uniform(Duration::from_secs(10));
1092 policy.update_config(new_config);
1093
1094 assert_eq!(
1095 policy.get_timeout(CommandCategory::Quick),
1096 Duration::from_secs(10)
1097 );
1098 }
1099}