Skip to main content

rivet_envoy_client/
utils.rs

1use std::collections::HashMap;
2use std::future::Future;
3use std::pin::Pin;
4use std::time::Duration;
5
6use rand::Rng;
7#[cfg(target_arch = "wasm32")]
8use wasm_bindgen::{JsCast, JsValue};
9#[cfg(target_arch = "wasm32")]
10use wasm_bindgen_futures::JsFuture;
11
12/// Convert an ID (byte slice) to a hex string.
13pub fn id_to_str(id: &[u8]) -> String {
14	hex::encode(id)
15}
16
17/// Stringify an error for logging.
18pub fn stringify_error(error: &anyhow::Error) -> String {
19	format!("{error:#}")
20}
21
22/// Error returned when the envoy is shutting down.
23#[derive(Debug)]
24pub struct EnvoyShutdownError;
25
26impl std::fmt::Display for EnvoyShutdownError {
27	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28		write!(f, "envoy shut down")
29	}
30}
31
32impl std::error::Error for EnvoyShutdownError {}
33
34/// Error returned when a sent remote SQLite request may have completed but the
35/// WebSocket closed before the response arrived.
36#[derive(Debug)]
37pub struct RemoteSqliteIndeterminateResultError {
38	pub operation: &'static str,
39}
40
41impl std::fmt::Display for RemoteSqliteIndeterminateResultError {
42	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43		write!(
44			f,
45			"remote sqlite {} result is indeterminate after envoy disconnect",
46			self.operation
47		)
48	}
49}
50
51impl std::error::Error for RemoteSqliteIndeterminateResultError {}
52
53/// Error returned before a transaction-bound remote SQLite request is sent
54/// when its owning WebSocket session is no longer current.
55#[derive(Debug)]
56pub struct RemoteSqliteConnectionSessionLostError {
57	pub expected: u64,
58	pub current: Option<u64>,
59}
60
61impl std::fmt::Display for RemoteSqliteConnectionSessionLostError {
62	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63		write!(
64			f,
65			"remote sqlite transaction connection session {} is no longer active",
66			self.expected
67		)
68	}
69}
70
71impl std::error::Error for RemoteSqliteConnectionSessionLostError {}
72
73/// Inject artificial latency for testing.
74pub async fn inject_latency(ms: Option<u64>) {
75	if let Some(ms) = ms {
76		if ms > 0 {
77			sleep(Duration::from_millis(ms)).await;
78		}
79	}
80}
81
82#[cfg(not(target_arch = "wasm32"))]
83pub type SleepFuture = Pin<Box<dyn Future<Output = ()> + Send>>;
84#[cfg(target_arch = "wasm32")]
85pub type SleepFuture = Pin<Box<dyn Future<Output = ()>>>;
86
87pub fn boxed_sleep(duration: Duration) -> SleepFuture {
88	Box::pin(sleep(duration))
89}
90
91#[cfg(not(target_arch = "wasm32"))]
92pub async fn sleep(duration: Duration) {
93	tokio::time::sleep(duration).await;
94}
95
96#[cfg(target_arch = "wasm32")]
97pub async fn sleep(duration: Duration) {
98	let delay_ms = duration.as_millis().min(u32::MAX as u128) as f64;
99	let promise = js_sys::Promise::new(&mut |resolve, _reject| {
100		let global = js_sys::global();
101		let set_timeout = js_sys::Reflect::get(&global, &JsValue::from_str("setTimeout"))
102			.ok()
103			.and_then(|value| value.dyn_into::<js_sys::Function>().ok());
104
105		if let Some(set_timeout) = set_timeout {
106			let _ = set_timeout.call2(&global, &resolve, &JsValue::from_f64(delay_ms));
107		} else {
108			let _ = resolve.call0(&JsValue::UNDEFINED);
109		}
110	});
111
112	let _ = JsFuture::from(promise).await;
113}
114
115#[cfg(not(target_arch = "wasm32"))]
116pub fn spawn_detached<F>(future: F)
117where
118	F: Future<Output = ()> + Send + 'static,
119{
120	tokio::spawn(future);
121}
122
123#[cfg(target_arch = "wasm32")]
124pub fn spawn_detached<F>(future: F)
125where
126	F: Future<Output = ()> + 'static,
127{
128	tokio::task::spawn_local(future);
129}
130
131pub struct BackoffOptions {
132	pub initial_delay: u64,
133	pub max_delay: u64,
134	pub multiplier: f64,
135	pub jitter: bool,
136}
137
138impl Default for BackoffOptions {
139	fn default() -> Self {
140		Self {
141			initial_delay: 1000,
142			max_delay: 30000,
143			multiplier: 2.0,
144			jitter: true,
145		}
146	}
147}
148
149pub fn calculate_backoff(attempt: u32, options: &BackoffOptions) -> Duration {
150	let delay = (options.initial_delay as f64 * options.multiplier.powi(attempt as i32))
151		.min(options.max_delay as f64);
152
153	let delay = if options.jitter {
154		let jitter = rand::thread_rng().gen_range(0.0..0.25);
155		delay * (1.0 + jitter)
156	} else {
157		delay
158	};
159
160	Duration::from_millis(delay as u64)
161}
162
163pub struct ParsedCloseReason {
164	pub group: String,
165	pub error: String,
166	pub ray_id: Option<String>,
167}
168
169pub fn parse_ws_close_reason(reason: &str) -> Option<ParsedCloseReason> {
170	let (main_part, ray_id) = match reason.split_once('#') {
171		Some((main, ray)) => (main, Some(ray.to_string())),
172		None => (reason, None),
173	};
174
175	let (group, error) = main_part.split_once('.')?;
176
177	if group.is_empty() || error.is_empty() {
178		tracing::warn!(%reason, "failed to parse close reason");
179		return None;
180	}
181
182	Some(ParsedCloseReason {
183		group: group.to_string(),
184		error: error.to_string(),
185		ray_id,
186	})
187}
188
189const U16_MAX: u32 = 65535;
190
191pub fn wrapping_add_u16(a: u16, b: u16) -> u16 {
192	a.wrapping_add(b)
193}
194
195pub fn wrapping_sub_u16(a: u16, b: u16) -> u16 {
196	a.wrapping_sub(b)
197}
198
199pub fn wrapping_gt_u16(a: u16, b: u16) -> bool {
200	a != b && (a.wrapping_sub(b) as u32) < U16_MAX / 2
201}
202
203pub fn wrapping_lt_u16(a: u16, b: u16) -> bool {
204	a != b && (b.wrapping_sub(a) as u32) < U16_MAX / 2
205}
206
207pub fn wrapping_gte_u16(a: u16, b: u16) -> bool {
208	a == b || wrapping_gt_u16(a, b)
209}
210
211pub fn wrapping_lte_u16(a: u16, b: u16) -> bool {
212	a == b || wrapping_lt_u16(a, b)
213}
214
215/// Hash-map keyed by multiple byte buffers (equivalent of TS BufferMap).
216pub struct BufferMap<T> {
217	inner: HashMap<String, T>,
218}
219
220impl<T> BufferMap<T> {
221	pub fn new() -> Self {
222		Self {
223			inner: HashMap::new(),
224		}
225	}
226
227	pub fn get(&self, buffers: &[&[u8]]) -> Option<&T> {
228		self.inner.get(&cyrb53(buffers))
229	}
230
231	pub fn get_mut(&mut self, buffers: &[&[u8]]) -> Option<&mut T> {
232		self.inner.get_mut(&cyrb53(buffers))
233	}
234
235	pub fn insert(&mut self, buffers: &[&[u8]], value: T) {
236		self.inner.insert(cyrb53(buffers), value);
237	}
238
239	pub fn remove(&mut self, buffers: &[&[u8]]) -> Option<T> {
240		self.inner.remove(&cyrb53(buffers))
241	}
242
243	pub fn contains_key(&self, buffers: &[&[u8]]) -> bool {
244		self.inner.contains_key(&cyrb53(buffers))
245	}
246
247	pub fn remove_where(&mut self, mut predicate: impl FnMut(&T) -> bool) -> Vec<T> {
248		let keys = self
249			.inner
250			.iter()
251			.filter_map(|(key, value)| predicate(value).then_some(key.clone()))
252			.collect::<Vec<_>>();
253		keys.into_iter()
254			.filter_map(|key| self.inner.remove(&key))
255			.collect()
256	}
257}
258
259impl<T> Default for BufferMap<T> {
260	fn default() -> Self {
261		Self::new()
262	}
263}
264
265fn cyrb53(buffers: &[&[u8]]) -> String {
266	let (mut h1, mut h2): (u32, u32) = (0xdeadbeef, 0x41c6ce57);
267	for buffer in buffers {
268		for &b in *buffer {
269			h1 = (h1 ^ b as u32).wrapping_mul(2654435761);
270			h2 = (h2 ^ b as u32).wrapping_mul(1597334677);
271		}
272	}
273	h1 = (h1 ^ (h1 >> 16)).wrapping_mul(2246822507) ^ (h2 ^ (h2 >> 13)).wrapping_mul(3266489909);
274	h2 = (h2 ^ (h2 >> 16)).wrapping_mul(2246822507) ^ (h1 ^ (h1 >> 13)).wrapping_mul(3266489909);
275	let result = (2097151 & h2 as u64) * 4294967296 + h1 as u64;
276	format!("{result:x}")
277}