tauri_runtime_cef/
devtools.rs1use std::sync::atomic::{AtomicI32, Ordering};
6
7const RESERVED_RANGE_START: i32 = 1_000_000_000;
15
16static NEXT_MESSAGE_ID: AtomicI32 = AtomicI32::new(1);
17static NEXT_RESERVED_MESSAGE_ID: AtomicI32 = AtomicI32::new(RESERVED_RANGE_START);
18
19#[derive(Clone, Copy, Debug)]
21pub struct DevToolsMessageIdExhausted;
22
23impl std::fmt::Display for DevToolsMessageIdExhausted {
24 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 formatter.write_str("native CEF DevTools message identifiers are exhausted")
26 }
27}
28
29impl std::error::Error for DevToolsMessageIdExhausted {}
30
31pub fn allocate_devtools_message_id() -> Result<i32, DevToolsMessageIdExhausted> {
41 allocate_from(&NEXT_MESSAGE_ID, RESERVED_RANGE_START - 1)
42}
43
44pub(crate) fn allocate_runtime_devtools_message_id() -> Result<i32, DevToolsMessageIdExhausted> {
51 allocate_from(&NEXT_RESERVED_MESSAGE_ID, i32::MAX)
52}
53
54fn allocate_from(counter: &AtomicI32, last: i32) -> Result<i32, DevToolsMessageIdExhausted> {
58 counter
59 .try_update(Ordering::Relaxed, Ordering::Relaxed, |value| {
60 if value > last {
61 return None;
62 }
63 value.checked_add(1)
64 })
65 .map_err(|_| DevToolsMessageIdExhausted)
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use std::collections::HashSet;
72
73 #[test]
74 fn concurrent_native_producers_never_share_a_response_id() {
75 let threads = (0..4)
76 .map(|_| {
77 std::thread::spawn(|| {
78 (0..1_000)
79 .map(|_| allocate_devtools_message_id().unwrap())
80 .collect::<Vec<_>>()
81 })
82 })
83 .collect::<Vec<_>>();
84 let ids = threads
85 .into_iter()
86 .flat_map(|thread| thread.join().unwrap())
87 .collect::<Vec<_>>();
88 assert!(ids.iter().all(|id| *id > 0));
89 assert_eq!(ids.iter().collect::<HashSet<_>>().len(), 4_000);
90 }
91
92 #[test]
93 fn callers_and_the_runtime_never_share_a_response_id() {
94 let caller_ids = (0..1_000)
95 .map(|_| allocate_devtools_message_id().unwrap())
96 .collect::<HashSet<_>>();
97 let runtime_ids = (0..1_000)
98 .map(|_| allocate_runtime_devtools_message_id().unwrap())
99 .collect::<HashSet<_>>();
100 assert!(
101 caller_ids
102 .iter()
103 .all(|id| *id > 0 && *id < RESERVED_RANGE_START)
104 );
105 assert!(runtime_ids.iter().all(|id| *id >= RESERVED_RANGE_START));
106 assert!(caller_ids.is_disjoint(&runtime_ids));
107 }
108
109 #[test]
110 fn caller_exhaustion_cannot_reach_the_reserved_range() {
111 let last = RESERVED_RANGE_START - 1;
112 let counter = AtomicI32::new(last);
113 assert_eq!(allocate_from(&counter, last).unwrap(), last);
114 assert!(allocate_from(&counter, last).is_err());
115 assert!(allocate_from(&counter, last).is_err());
116 assert_eq!(counter.load(Ordering::Relaxed), RESERVED_RANGE_START);
117 }
118
119 #[test]
120 fn exhaustion_cannot_reuse_a_late_response_id() {
121 let counter = AtomicI32::new(i32::MAX - 1);
122 assert_eq!(allocate_from(&counter, i32::MAX).unwrap(), i32::MAX - 1);
123 assert!(allocate_from(&counter, i32::MAX).is_err());
124 assert!(allocate_from(&counter, i32::MAX).is_err());
125 assert_eq!(counter.load(Ordering::Relaxed), i32::MAX);
126 }
127}