surrealcs_kernel/
allocator.rs

1//! An allocator for mapping an index to a value T.
2use std::collections::VecDeque;
3
4use nanoservices_utils::errors::{NanoServiceError, NanoServiceErrorStatus};
5
6/// An allocator for mapping an index to a value T.
7///
8/// # Fields
9/// * `connection_pool` - A vector of optional values of type T that can be allocated and deallocated, and accessed by index.
10/// * `free_connections` - A queue of indices that have been deallocated and are available for allocation.
11/// * `allocated_connections` - A queue of indices that have been allocated and are currently in use.
12#[derive(Debug)]
13pub struct Allocator<T: Clone> {
14	pub connection_pool: Vec<Option<T>>,
15	pub free_connections: VecDeque<usize>,
16	pub allocated_connections: VecDeque<usize>,
17}
18
19impl<T: Clone> Default for Allocator<T> {
20	fn default() -> Self {
21		Self::new()
22	}
23}
24
25impl<T: Clone> Allocator<T> {
26	/// The constructor for the Allocator struct.
27	///
28	/// # Returns
29	/// A new instance of the Allocator struct.
30	pub fn new() -> Self {
31		Self {
32			connection_pool: Vec::new(),
33			free_connections: VecDeque::new(),
34			allocated_connections: VecDeque::new(),
35		}
36	}
37
38	/// Allocates a connection in the connection pool.
39	///
40	/// # Arguments
41	/// * `connection` - The connection to be allocated.
42	///
43	/// # Returns
44	/// The index of the allocated connection to be used to access the connection in the connection pool.
45	pub fn allocate(&mut self, connection: T) -> usize {
46		if let Some(index) = self.free_connections.pop_front() {
47			self.connection_pool[index] = Some(connection);
48			self.allocated_connections.push_back(index);
49			index
50		} else {
51			self.connection_pool.push(Some(connection));
52			self.allocated_connections.push_back(self.connection_pool.len() - 1);
53			self.connection_pool.len() - 1
54		}
55	}
56
57	/// Yields the next allocated index in the connection pool.
58	///
59	/// # Returns
60	/// The next allocated index in the connection pool. (empty if thee is no allocated connection available)
61	pub fn yield_next_allocated_index(&mut self) -> Option<usize> {
62		// for some reason this line throws a warning, however, the index is not
63		// unused
64		#[allow(unused_assignments)]
65		let mut index: Option<usize> = None;
66
67		loop {
68			index = self.allocated_connections.pop_front();
69			if index.is_none() {
70				break;
71			}
72			let i = index.unwrap();
73			let connection = self.connection_pool[i].as_ref();
74			match connection {
75				Some(_) => {
76					self.allocated_connections.push_back(i);
77					break;
78				}
79				None => continue,
80			}
81		}
82		index
83	}
84
85	/// Extracts a connection from the connection pool.
86	///
87	/// # Arguments
88	/// * `index` - The index of the connection to be extracted.
89	///
90	/// # Returns
91	/// The connection at the specified index.
92	pub fn extract_connection(&self, index: usize) -> Result<(T, usize), NanoServiceError> {
93		if index + 1 > self.connection_pool.len() {
94			return Err(NanoServiceError::new(
95				format!("Connection at index {index} is unavailable"),
96				NanoServiceErrorStatus::Unknown,
97			));
98		}
99		let connection = self.connection_pool[index].as_ref();
100		match connection {
101			Some(c) => Ok((c.clone(), index)),
102			None => Err(NanoServiceError::new(
103				format!("Connection at index {index} is deallocated"),
104				NanoServiceErrorStatus::Unknown,
105			)),
106		}
107	}
108
109	/// Deallocates a connection in the connection pool.
110	///
111	/// # Arguments
112	/// * `index` - The index of the connection to be deallocated.
113	pub fn deallocate(&mut self, index: usize) -> Result<T, NanoServiceError> {
114		if index + 1 > self.connection_pool.len() {
115			return Err(NanoServiceError::new(
116				format!("Connection at index {index} is unavailable"),
117				NanoServiceErrorStatus::Unknown,
118			));
119		}
120		let placeholder = self.connection_pool[index].take();
121		if placeholder.is_none() {
122			return Err(NanoServiceError::new(
123				format!("Connection at index {index} is already deallocated"),
124				NanoServiceErrorStatus::Unknown,
125			));
126		}
127		self.free_connections.push_back(index);
128		Ok(placeholder.unwrap())
129	}
130
131	/// Yields all the connections in the connection pool.
132	///
133	/// # Returns
134	/// A vector of all the connections in the connection pool.
135	pub fn yield_connections(&self) -> Vec<T> {
136		let mut connections = Vec::new();
137		for i in &self.allocated_connections {
138			let connection = self.connection_pool[*i].as_ref();
139			match connection {
140				Some(c) => connections.push(c.clone()),
141				None => continue,
142			}
143		}
144		connections
145	}
146}
147
148#[cfg(test)]
149mod tests {
150
151	use super::*;
152
153	#[test]
154	fn test_new() {
155		let allocator = Allocator::<usize>::new();
156		assert_eq!(allocator.connection_pool.len(), 0);
157		assert_eq!(allocator.free_connections.len(), 0);
158		assert_eq!(allocator.allocated_connections.len(), 0);
159	}
160
161	#[test]
162	fn test_allocate() {
163		let mut allocator = Allocator::<usize>::new();
164		let index = allocator.allocate(5);
165		assert_eq!(index, 0);
166		assert_eq!(allocator.connection_pool[index], Some(5));
167		assert_eq!(allocator.allocated_connections.len(), 1);
168		assert_eq!(allocator.free_connections.len(), 0);
169		assert_eq!(allocator.connection_pool.len(), 1);
170
171		// ensures that there is only one connection in the pool
172		assert_eq!(allocator.yield_next_allocated_index(), Some(0));
173		assert_eq!(allocator.yield_next_allocated_index(), Some(0));
174		assert_eq!(allocator.yield_next_allocated_index(), Some(0));
175
176		let index = allocator.allocate(6);
177		assert_eq!(index, 1);
178		assert_eq!(allocator.connection_pool[index], Some(6));
179		assert_eq!(allocator.allocated_connections.len(), 2);
180		assert_eq!(allocator.free_connections.len(), 0);
181
182		// ensures that we cycle between the two connections
183		assert_eq!(allocator.yield_next_allocated_index(), Some(0));
184		assert_eq!(allocator.yield_next_allocated_index(), Some(1));
185		assert_eq!(allocator.yield_next_allocated_index(), Some(0));
186		assert_eq!(allocator.yield_next_allocated_index(), Some(1));
187	}
188
189	#[test]
190	fn test_deallocate() {
191		let mut allocator = Allocator::<usize>::new();
192		let index = allocator.allocate(5);
193		let index_two = allocator.allocate(6);
194
195		assert_eq!(index, 0);
196		assert_eq!(index_two, 1);
197		assert_eq!(allocator.connection_pool[index_two], Some(6));
198		assert_eq!(allocator.allocated_connections.len(), 2);
199		assert_eq!(allocator.free_connections.len(), 0);
200
201		// ensures that we cycle between the two connections
202		assert_eq!(allocator.yield_next_allocated_index(), Some(0));
203		assert_eq!(allocator.yield_next_allocated_index(), Some(1));
204		assert_eq!(allocator.yield_next_allocated_index(), Some(0));
205		assert_eq!(allocator.yield_next_allocated_index(), Some(1));
206
207		assert_eq!(Ok(5), allocator.deallocate(0));
208		assert_eq!(allocator.connection_pool[0], None);
209		assert_eq!(allocator.allocated_connections.len(), 2);
210		assert_eq!(allocator.free_connections.len(), 1);
211		assert_eq!(allocator.free_connections[0], 0);
212
213		// only yields the second connection
214		assert_eq!(allocator.yield_next_allocated_index(), Some(1));
215		assert_eq!(allocator.yield_next_allocated_index(), Some(1));
216		assert_eq!(allocator.yield_next_allocated_index(), Some(1));
217		assert_eq!(allocator.yield_next_allocated_index(), Some(1));
218		assert_eq!(allocator.yield_next_allocated_index(), Some(1));
219
220		// ensure that the first dead connection is recycled
221		let index = allocator.allocate(7);
222		assert_eq!(index, 0);
223		assert_eq!(allocator.connection_pool[index], Some(7));
224
225		// ensures that we cycle between the two connections
226		assert_eq!(allocator.yield_next_allocated_index(), Some(1));
227		assert_eq!(allocator.yield_next_allocated_index(), Some(0));
228		assert_eq!(allocator.yield_next_allocated_index(), Some(1));
229		assert_eq!(allocator.yield_next_allocated_index(), Some(0));
230		assert_eq!(allocator.yield_next_allocated_index(), Some(1));
231
232		// test an allocation that is out of bounds
233		let result = allocator.deallocate(20);
234		assert!(result.is_err());
235		if let Err(e) = result {
236			assert_eq!(e.message, "Connection at index 20 is unavailable");
237		}
238	}
239
240	#[test]
241	fn test_double_deallocate() {
242		let mut allocator = Allocator::<usize>::new();
243		let _ = allocator.allocate(5);
244		let _ = allocator.allocate(6);
245
246		assert_eq!(Ok(5), allocator.deallocate(0));
247		assert_eq!(Ok(6), allocator.deallocate(1));
248
249		assert_eq!(allocator.connection_pool[0], None);
250		assert_eq!(allocator.connection_pool[1], None);
251		assert_eq!(allocator.allocated_connections.len(), 2);
252		assert_eq!(allocator.free_connections.len(), 2);
253		assert_eq!(allocator.free_connections[0], 0);
254		assert_eq!(allocator.free_connections[1], 1);
255
256		// deallocate something that has already been deallocated
257		let result = allocator.deallocate(0);
258		assert!(result.is_err());
259		if let Err(e) = result {
260			assert_eq!(e.message, "Connection at index 0 is already deallocated");
261		}
262	}
263
264	#[test]
265	fn test_extract_connection() {
266		let mut allocator = Allocator::<usize>::new();
267
268		let outcome = allocator.extract_connection(20);
269		assert!(outcome.is_err());
270		if let Err(e) = outcome {
271			assert_eq!(e.message, "Connection at index 20 is unavailable");
272		}
273
274		let index = allocator.allocate(5);
275		let connection = allocator.extract_connection(index);
276		let result = connection.unwrap();
277		assert_eq!(result.0, 5);
278		assert_eq!(result.1, 0);
279
280		let _ = allocator.deallocate(index);
281		let outcome = allocator.extract_connection(index);
282		assert!(outcome.is_err());
283		if let Err(e) = outcome {
284			assert_eq!(e.message, "Connection at index 0 is deallocated");
285		}
286	}
287
288	#[test]
289	fn test_yield_connections() {
290		let mut allocator = Allocator::<usize>::new();
291		let _ = allocator.allocate(5);
292		let _ = allocator.allocate(6);
293
294		let connections = allocator.yield_connections();
295		assert_eq!(connections.len(), 2);
296		assert_eq!(connections[0], 5);
297		assert_eq!(connections[1], 6);
298
299		let _ = allocator.deallocate(0);
300		let connections = allocator.yield_connections();
301		assert_eq!(connections.len(), 1);
302		assert_eq!(connections[0], 6);
303	}
304}