1 个稳定版本
1.0.0 | 2021年8月27日 |
---|
在 内存管理 中排名 #475
66KB
539 行
generic_pool
一个用于回收分配的对象以便以后重用的池。使用泛型get/put方法,您可以将(几乎)任何类型存储在单个池实例中。
与其他池相比,此库的主要优势是它可以无缝地存储和检索任意数量的不同对象。池本身不包含任何泛型,只有get和put方法。您初始化池并添加您想要回收的任何对象,当您以后需要它们时,只需告诉池您想要哪种类型的对象。内部实现会执行所有选择正确对象类型的魔法。
此库100%是纯Rust,不使用不稳定或nightly功能,最重要的是,不包含任何不安全代码。它也没有对默认的任何依赖,但有一个可选的serde功能,可以通过Serde进行配置的序列化和反序列化。
功能
- 提供轻量级的普通版本以及线程安全的同步版本。
- 可选提供<强>drop guard强>,它们会在对象超出作用域后自动将其添加回存储。
- 允许配置存储对象的最多数量,以防止RAM耗尽。
- 允许配置内部容量随时间增加的速度。
- 可以为每个对象类型单独设置每个配置选项。
- 您还可以为没有设置自定义配置的对象类型设置回退配置。
- 提供可选的自动创建实现
Default
trait的对象。 - 提供便宜
Clon
ing,因此您可以在许多地方轻松使用相同的池。
快速示例
此示例演示了最基本的使用方法。我们定义了两个不同的结构,Person
和 Animal
。我们将它们全部插入到池中,并在稍后检索它们。请注意,池确保返回正确的对象类型。
use generic_pool::Pool;
#[derive(Debug, Default, Clone, Eq, PartialEq)]
struct Person {
name: String,
age: i32,
}
#[derive(Debug, Default, Clone, Eq, PartialEq)]
struct Animal {
home: bool,
name: String,
age_in_month: u64,
}
fn main() {
// Initialize the pool. Note that there is no need to specify any generic type parameters.
let mut pool = Pool::default();
let p = Person {
name: String::from("Mark"),
age: 100,
};
let a = Animal {
home: true,
name: String::from("Meow"),
age_in_month: 12,
};
// Add copies of the objects to the pool. Note that the pool takes any object.
pool.put(p.clone());
pool.put(a.clone());
// Retrieve the objects from the pool.
let person: Person = pool.get().unwrap(); // Returns `Option<Person>`.
let animal: Animal = pool.get_or_default(); // Returns `Animal`, but requires `Animal` to implement Default.
// The objects we retrieve are exactly the ones we put into the pool earlier.
assert_eq!(person, p);
assert_eq!(animal, a);
// NOTE: You should implement some kind of reset logic for objects saved in the pool.
// E.g.: person.reset()
}
工作原理
这个库利用了Any
特质,它为每个对象提供了一个全局唯一的TypeId
。
这个TypeId
被用作HashMap
中的键,其中值是实现了Any
特质的boxed 特质对象的Vec。
从池中添加/获取对象时,使用TypeId
检索正确的Vec,值被推入或弹出。
获取值时,我们使用Box.downcast()
将boxed特质对象转换回具体类型的boxed版本。之后,我们可以通过解引用它来简单地移动具体对象,请参阅这个Stackoverflow问题的接受答案。
安全性
池不会以任何方式修改它接收或提供的对象,因此尤其不会正确地重置对象。 在使用对象之前或之后,重置对象是您的责任。 否则,您可能会意外地使用早期操作中的数据,从而打开安全漏洞。
一个常见的坏例子
许多前端API也提供如果接收了正确的凭据,则提供特权访问。根据实现方式,管理员标志可能只有在发送了特殊凭据时才会显式设置。如果不重置回收请求对象的管理员标志,这可能会打开一个很大的安全漏洞。
use generic_pool::Pool;
struct FrontendRequest {
admin_credentials: Option<String>,
commands: Vec<String>,
}
#[derive(Default)]
struct BackendRequest {
is_admin: bool,
commands: Vec<Command>,
}
fn main() {
let mut pool = Pool::default();
/* initialize API... */
loop {
// Retrieve a frontend request.
let frontend_req: FrontendRequest = get_frontend_req();
// Retrieve a recycled backend request object from the pool.
let mut backend_req = pool.get_or_default_with_guard::<BackendRequest>();
/* HAZARD - The backend request object might still contain older commands. */
// Parse the commands and check if they are known and valid.
for cmd in frountend_req.commands.iter() {
match parse_cmd(cmd) {
Ok(parsed_cmd) => backend_req.commands.push(parsed_cmd),
Err(err) => return_error_to_client_and_abort(err),
}
}
/* HAZARD - the backend request might still have the admin flag set!!! */
if let Some(credentials) = &frontend_req.admin_credentials {
match check_admin_credentials(credentials) {
Ok(_) => backend_req.is_admin = true,
Err(err) => return_error_to_client_and_abort(err),
}
}
// The backend might now receive unintended commands or even commands
// from unpriviledged users with the admin flag set.
send_backend_request(backend_req);
// NOTE: The drop guard of the backend request will put it back into the pool now.
}
}
解决方案
当然,一个简单的解决方案是将整个解析和检查过程实现为以这种方式,即所有回收对象的任何字段总是用完全新的数据填充,但这可能并不总是受欢迎。检查您回收的所有对象的所有字段在使用前是否都被覆盖可能会有困难。
因此,最安全的解决方案是为所有使用的对象编写一些显式的重置逻辑,并确保在从池中检索对象时调用它。
use generic_pool::{Pool, DropGuard};
/// A local trait providing the reset logic for all recycled objects.
trait Reset {
fn reset(self) -> Self; // See below why we use this pattern.
}
struct FrontendRequest {
admin_credentials: Option<String>,
commands: Vec<String>,
}
#[derive(Default)]
struct BackendRequest {
is_admin: bool,
commands: Vec<Command>,
}
// We implement the trait on any drop guard by this.
impl<G: AsMut<BackendRequest>> Reset for G {
fn reset(mut self) -> Self {
let req = self.as_mut();
req.is_admin = false;
req.commands.clear();
self
}
}
fn main() {
let mut pool = Pool::default();
/* initialize API... */
loop {
// Retrieve a frontend request.
let frontend_req: FrontendRequest = get_frontend_req();
// Retrieve a recycled backend request object from the pool.
let mut backend_req = match pool.get_with_guard::<BackendRequest>() {
Some(req) => req.reset(), // Is an expression, gives back req
None => DropGuard::new(BackendRequest::default(), &pool), // No need to reset
};
/* We can proceed safely now */
}
}
配置
您可以配置存储内部缓冲区的最大大小、初始大小以及容量增加的速率。这些设置始终适用于存储在池中的特定类型的对象,而不是整个池。也就是说,如果您存储例如2个不同的对象类型,并配置默认最大容量为1_000,则池将存储多达2_000个对象。这些设置旨在限制池的内存使用。
池的默认配置将适用于所有对象类型,直到您为对象类型设置一个自定义配置。如果您不为池指定自定义默认配置,则池将使用Config
对象的默认值。
示例
use generic_pool::{Pool, Config};
fn main() {
// Use a non-default config.
let config = Config {
max: 1_000,
step: 100,
start: 500,
};
assert_ne!(config, Config::default());
let mut pool = Pool::with_default_config(config); // NOTE Config implements Copy.
// Alternative:
// let mut pool = Pool::default();
// pool.set_default_config(config);
assert_eq!(config, pool.get_default_config());
assert_eq!(config, pool.get_config::<Vec<u8>>());
// Create a costum config for `Vec<u8>`.
let custom_config = Config {
max: 100,
step: 10,
start: 10,
};
pool.set_config::<Vec<u8>>(custom_config);
assert_eq!(custom_config, pool.get_config::<Vec<u8>>());
}
许可证
此库根据MIT许可证授权。
贡献
除非您明确声明,否则您有意提交以包括在此库中的任何贡献,均应按照MIT许可证授权,不附加任何额外条款或条件。
依赖项
~175KB