我有一个 Rutler NIF,大部分时间都在工作,但我遇到了一个奇怪的间歇性(不可预测,但并非不常见)ArgumentError 失败,我既找不到原因,也不知道如何正确调试。
我从 GenServer 创建 ResourceArc,它在其状态下保存 ref,然后我使用 Poolboy 来控制对 GenServer 的访问(然后是 ResourceArc 的 ref 句柄)。它工作了一段时间,直到我得到一个 ArgumentError。我已将其缩小到 ref 处于 Rustler 未将其视为 ResourceArc 的状态,因此抛出 ArgumentError。当然,我可以在发生这种情况时重新创建 ResourceArc,但这是一个相对昂贵的设置(几秒钟),我想知道是什么导致了这种情况,看看我的代码中是否有可以更改的内容来避免这个问题,或者至少减少它的频率。此外,失败请求之前的请求并没有失败,所以我不相信之前的请求导致了错误,然后检查了错误的引用。
这是代码。
长生不老药:
泳池男孩:
conf = [
name: {:local, :model},
worker_module: MyApp.ModelServer,
size: 1,
max_overflow: 0
]
:poolboy.child_spec(:model, conf, [model_arg])
GenServer:
defmodule MyApp.ModelServer do
use GenServer
def start_link(model_arg), do: GenServer.start_link(__MODULE__, model_arg, [])
def fetch_handle(pid), do: GenServer.call(pid, :fetch_handle)
def init(model_arg), do: get_resource(model_arg)
def handle_call(:fetch_handle, _from, state), do: {:reply, state, state}
defp get_resource(model_arg) do
case MyApp.Native.create_model(model_arg) do
{:error, _} = err -> err
ref -> {:ok, ref}
end
end
end
如何称呼:
:poolboy.transaction(:model, fn pid ->
handle = MyApp.ModelServer.fetch_handle(pid)
MyApp.Native.call_model(handle, ["some model input"])
end)
这是 Rust 代码。 我在文件顶部删除了一些不相关的东西、原子宏和
use
东西以简化它,但为了完整起见,正在创建的“模型”是 rust-伯特图书馆。
pub struct ModelContainer {
model: Model,
}
pub struct ModelResource(Mutex<ModelContainer>);
impl ModelResource {
fn new(input: &str) -> Option<Self> {
match ModelContainer::new(input) {
Some(mc) => return Some(ModelResource(Mutex::new(mc))),
None => return None,
}
}
}
impl ModelContainer {
fn new(input: &str) -> Option<Self> {
let model = create_model(input)?;
Some(Self {
model: model,
})
}
}
pub fn create_model(input: &str) -> Option<Model> {
match ModelModel::new(input) {
Ok(model) => Some(model),
Err(_) => {
return None;
}
}
}
#[rustler::nif]
pub fn create_model<'a>(input: &str) -> Result<ResourceArc<ModelResource>, Error> {
match ModelResource::new(input) {
Some(tmr) => return Ok(ResourceArc::new(tmr)),
None => return Err(Error::Term(Box::new(atoms::error_creating_model()))),
}
}
#[rustler::nif(schedule = "DirtyCpu")]
pub fn call_model(
resource: ResourceArc<ModelResource>,
input: Vec<String>,
) -> Result<Vec<String>, Error> {
let lock = resource.0.lock().unwrap();
match lock.model.call_model(&input) {
Ok(vec) => {
let strs: Vec<String> = vec.iter().map(|i| i.trim_start().into()).collect();
return Ok(strs);
}
Err(_) => return Err(Error::Term(Box::new(atoms::error_translating_text()))),
}
}
fn load(env: Env, _info: Term) -> bool {
rustler::resource!(ModelResource, env);
true
}
rustler::init!(
"Elixir.MyApp.Native",
[create_model, call_model],
load = load
);
一切都会成功。我可以设置模型/ResourceArc。我可以调用它并获得成功的响应,一会儿,然后最终它会遇到这个。
** (ArgumentError) argument error
(my_app 0.1.0) MyApp.Native.call_model(#Reference<0.1875845148.3104178182.117090>, ["model input here"])
知道我该如何调试吗?或者我设置或存储我的 ResourceArc 的方式有什么问题会导致它过早地被垃圾收集?就好像 Rust 方面正在删除模型,但 Elixir 方面仍然拥有参考。
似乎 ResourceArc 在 Rust 中被过早地删除,而 Elixir 方面仍然持有对它的引用。一个可能的原因可能是在
Mutex
结构中使用了 ModelResource
,这可能会导致死锁和意外行为。
您可以尝试使用
RwLock
而不是 Mutex
,它一次允许多个读者但只允许一个作者。这可以防止死锁并确保资源在线程之间正确共享。
另一种可能是 Rustler 资源由于内存不足或其他资源限制而被 Erlang VM 垃圾收集。您可以通过在 Rust 代码中添加日志语句并检查资源是否被意外删除来检查是否属于这种情况。您还可以尝试增加内存限制或调整 VM 中的其他资源设置以查看是否有帮助。
如果这些解决方案都不起作用,您可以考虑使用不同的 Rust 库来创建和管理翻译模型,或者探索其他可能更健壮和可靠的 Elixir 和 Rust 接口方式。